OpenResty 过滤响应体

Posted by zhuizhuhaomeng Blog on July 7, 2023

响应体过滤的问题

很多初学者在使用 OpenResty 过滤响应体的时候总是会遇到错误而调试半天。

最常见的就是没有先把 Content-Length 的响应头清空。 因为过滤响应体基本上是会修改响应体的大小,如果不删除响应头的 Content-Length, 那么响应体大小和 Content-Length 的值就对不上,这就导致协议错误。

另外一个是要对响应体进行修改,最好是未压缩的响应体,否则还需要进行解压才有办法执行替换。 但是具体的策略需要根据情况进行分析后进行选择,因为未压缩的文件可能是压缩后的好几倍, 这个不仅会增加网络传输的耗时并且会增加服务器的宽带费用。

当然,我们这里进行过滤之后会导致数据是未压缩的,因此也会导致数据的膨胀。 如果要处理该问题,那么可以使用子请求进行全缓冲,然后再发送响应。

服务器返回未压缩的数据

如果让服务器之间返回未压缩的报文,那么这时候就需要在发送给上游请求的时候把 Accept-Encoding 的头部去掉。

下面这是一个配置示例,展示了对上游未压缩的响应体进行替换的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
location ~ /css {
    proxy_set_header Accept-Encoding "";
    proxy_set_header Connection "";
    proxy_http_version 1.1;
    proxy_set_header Host $host;

    header_filter_by_lua_block {
        ngx.header.content_length = ni
    }

    body_filter_by_lua_block {
        -- ngx.arg[1] 代表输入的 chunk
        local chunk, eof = ngx.arg[1], ngx.arg[2]
        local buffers = ngx.ctx.buffers
        if not buffered then
           buffers = {}
           ngx.ctx.buffers = buffers
           ngx.ctx.buffers_cnt = 0
        end

        if chunk ~= "" then
           local cnt = ngx.ctx.buffers_cnt + 1
           ngx.ctx.buffers_cnt = cnt
           buffers[cnt] = chunk
           ngx.arg[1] = nil
        end

        if eof then
           local body = table.concat(buffers)
           ngx.ctx.buffers = nil

           body = ngx.re.gsub(body, "<img name='xxx'>",  "<img name='xxx', attr='hide'>", "jo")
           ngx.arg[1] = body
        end
    }
}

服务器返回压缩后的数据

下面这个示例设置为接受 gzip 类型的压缩数据。

使用到的第三方库:https://github.com/brimworks/lua-zlib。编译命令如下:

1
2
3
4
5
git clone git@github.com:brimworks/lua-zlib.git
cd lua-zlib
mkdir build
cmake -DLUA_INCLUDE_DIR=/usr/local/openresty/luajit/include/luajit-2.1 -DLUA_LIBRARIES=/usr/local/openresty/luajit/lib -DUSE_LUAJIT=ON -DUSE_LUA=OFF  ../
sudo cp zlib.so /usr/local/openresty/lualib
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
location ~ /css {
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_set_header Accept-Encoding "gzip";
    proxy_set_header Host $host;

    proxy_pass http://upstream;

    header_filter_by_lua_block {
        ngx.header.content_length = ni
        ngx.header.content_encoding = nil
    }

    body_filter_by_lua_block {
        -- ngx.arg[1] 代表输入的 chunk
        local zlib = require 'zlib'
        local chunk, eof = ngx.arg[1], ngx.arg[2]
        local buffers = ngx.ctx.buffers
        if not buffers then
           buffers = {}
           ngx.ctx.buffers = buffers
           ngx.ctx.buffers_cnt = 0
        end

        if chunk ~= "" then
           local cnt = ngx.ctx.buffers_cnt + 1
           ngx.ctx.buffers_cnt = cnt
           buffers[cnt] = chunk
           ngx.arg[1] = nil
        end

        if eof then
            local body = table.concat(buffers)
            ngx.ctx.buffers = nil

            local inflate = zlib.inflate()

            local body, eos, bytes_in, bytes_out = inflate(body)
            if not eos then
                ngx.log(ngx.ERR, "not end of gzip stream found") -- just for debug
            end

            body = ngx.re.gsub(body, "<img name='xxx'>",  "<img name='xxx', attr='hide'>", "jo")
            ngx.arg[1] = body
        end
    }
}