Skip to content
portl4t edited this page Dec 20, 2014 · 8 revisions

Transform is usually used to filter the HTTP reponse body to the client.

How to set Content-Length in transform stage

The HTTP response header will be influnced if we want to transform the response body.

If we want to set Content-Length in response header, we have to determine the total output bytes before the transform handler returns any data, we can return nil if we can not decide the total output bytes. If we have to return data from transform handler more than once, we should use ts.http.resp_transform.set_downstream_bytes to inform ats the total output bytes previously.

Sometimes we want to compute the output bytes based on the input bytes which can be retrieved by ts.http.resp_transform.get_upstream_bytes, however, this is not always true because we may get TS_LUA_INT64_MAX which indicates that we do not know how many bytes will be received from upstream at the beginning.

Here are some examples:

    1. We want to append abcdefg at the end of the response body, we can insure Content-Length will be set correctly unless the we got chunked response body from origin server.
function append_transform(data, eos)
    if ts.ctx['cset'] == false then
        local n = ts.http.resp_transform.get_upstream_bytes()
        if n ~= TS_LUA_INT64_MAX then
            ts.http.resp_transform.set_downstream_bytes(n+7)
        end
        ts.ctx['cset'] = true
    end

    if eos == 1 then
        return string.upper(data)..'abcdefg', eos
    else
        return string.upper(data), eos
    end
end

function do_remap()
    ts.hook(TS_LUA_RESPONSE_TRANSFORM, append_transform)
    ts.http.resp_cache_transformed(0)
    ts.http.resp_cache_untransformed(1)
    ts.ctx['cset'] = false
    return 0
end

Function ts.http.resp_transform.get_upstream_bytes will return TS_LUA_INT64_MAX if we got chunked response body from origin server.

    1. We want to replace abcdef with 1234567 in the response body and set Content-Length correctly unless we got chunked response body from origin server.
function do_replace(str)
   local newstr = ''
   local a, b, c = string.find(str, 'abcdef')
   while a ~= nil
   do
        newstr = newstr .. string.sub(str, 1, a-1) .. '1234567'
        str = string.sub(str, b+1)
        a, b, c = string.find(str, 'abcdef')
    end
    newstr = newstr .. str
    return newstr
end

function replace_transform(data, eos)
    ts.ctx['reserved'] = ts.ctx['reserved'] .. data
    if eos == 1 then
        local repl = do_replace(ts.ctx['reserved'])
        return repl, eos
    else
        return nil, eos
    end
end

function do_remap()
    ts.hook(TS_LUA_RESPONSE_TRANSFORM, replace_transform)
    ts.http.resp_cache_transformed(0)
    ts.http.resp_cache_untransformed(1)
    ts.ctx['reserved'] = ''
    return 0
end

As we can see, we will not return data from the transform handler unless there are no more input data from upstream, this will guarantee Content-Length will be set correctly unless we got chunked response body from origin server.

    1. We want to replace the response body with abcdefg
function rep_transform(data, eos)
    return 'abcdefg', 1
end

function do_remap()
    ts.hook(TS_LUA_RESPONSE_TRANSFORM, rep_transform)
    ts.http.resp_cache_transformed(0)
    ts.http.resp_cache_untransformed(1)
    return 0
end

The transform handler will be called only once here and Content-Length will be set correctly unless we got chunked response body from origin server.

Notice

Fact, we may want to set Content-Length correctly in example 2 and 3 even the orgin server issues chunked response body, because we write down only once in the transform handler and the size of the body can be ensured. This can be done by applying this patch TS-3252

Clone this wiki locally