最近公司前端框架组提了个需求,希望修改response中的一个css文件,去掉一个样式:max-width:1632px;。于是便想到了利用lua。
OpenResty lua编程相关资料
其中Readme要看完,是github上对OpenResty的lua-nginx-module比较全面的介绍。
Nginx处理的几个阶段 此处放上从网上找来的一幅图, 我这里修改response body显然是需要用到body_filter_by_lua*指令。
修改Response Body 修改Response Body的方式总体来说有4种,分别是:
1.使用 body_filter_by_lua 指令来实现:http://wiki.nginx.org/HttpLuaModule#body_filter_by_lua 这个支持流式处理。
2.使用 ngx.location.capture 发起子请求,然后对子请求的响应体进行全缓冲式修改
3.可以使用 ngx_replace_filter 模块 来进行流式正则替换 替换成的目标值可以通过 ngx_lua 模块嵌入一小段 Lua 代码来事先计算好,放置在你自己定义的 nginx 变量中,然后在 replace_filter 指令中直接引用之。比如
set_by_lua $my_var ‘… return …’; replace_filter ‘folderlist=w+’ ‘folderlist=$my_var’ ‘g’;
4.使用http_sub_module模块
根据实际的需求,使用第3种,需要先安装sregex library,然后重新编译安装OpenResty,在编译时 ./configure –add-module=/path/to/replace-filter-nginx-module 启用replace-filter-nginx-module模块。 如果使用第4种方式都需要重新编译安装OpenResty,在编译时 –with-http_sub_module 启用http_sub_module模块。 这两种他们都不接受,因为涉及的客户太多。 对于第2种方式,是个比较好的方式,但是使用第二种方式需要增加一个用于子请求的location,相当于大动了配置文件,并且相应的我还得去修改之前写的安装升级脚本,于是最终还是选择了第一种方式。 虽然第2种到第4种的方案不适用于此次的需求,但是我还是尝试了使用下第2种和第4种的方案,并会把相关的脚本和配置贴在下面。
第一种处理办法 lua脚本代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- body_filter_by_lua, body filter模块,ngx.arg[1]代表输入的chunk,ngx.arg[2]代表当前chunk是否为last
local chunk, eof = ngx.arg[1], ngx.arg[2]
local buffered = ngx.ctx.buffered
if not buffered then
buffered = {} -- XXX we can use table.new here
ngx.ctx.buffered = buffered
end
if chunk ~= "" then
buffered[#buffered + 1] = chunk
ngx.arg[1] = nil
end
if eof then
local whole = table.concat(buffered)
ngx.ctx.buffered = nil
-- try to unzip
-- local status, debody = pcall(com.decode, whole)
-- if status then whole = debody end
-- try to add or replace response body
-- local js_code = ...
-- whole = whole .. js_code
whole = string.gsub(whole, "max%-width%:1632px%;", "")
ngx.arg[1] = whole
end
Nginx相关location配置如下:
1
2
3
4
5
6
7
8
location /fe_components/ {
location ~ /fe_components/jqwidget/.*/bh-scenes-1.2.min.css {
root /usr/local/nginx/nginx/html/;
body_filter_by_lua_file /opt/lua/replace.lua;
}
root /usr/local/nginx/nginx/html;
}
但是重载Nginx后发现,这个css样式的响应时间竟然是1.1min,可怕。。。 仔细阅读上面贴出来的OpenResty Readme ,发现有这么一段话:
1
When the Lua code may change the length of the response body, then it is required to always clear out the Content-Length response header (if any) in a header filter to enforce streaming output, as in
1
2
3
4
5
6
location /foo {
# fastcgi_pass/proxy_pass/...
header_filter_by_lua_block { ngx.header.content_length = nil }
body_filter_by_lua 'ngx.arg[1] = string.len(ngx.arg[1]) .. "\n"';
}
于是需要修改配置文件,在body_filter_by_lua_file /opt/lua/replace.lua之前就得把header中的 content_length 置为空。
1
2
3
4
5
6
7
8
9
location /fe_components/ {
location ~ /fe_components/jqwidget/.*/bh-scenes-1.2.min.css {
root /usr/local/nginx/nginx/html/;
header_filter_by_lua 'ngx.header.content_length = nil';
body_filter_by_lua_file /opt/lua/replace.lua;
}
root /usr/local/nginx/nginx/html;
}
这样重载Nginx,清除缓存重新访问下,发现加载就正常了。 仔细了解了下原因,当代码运行到 body_filter_by_lua 时,HTTP 报头(header)已经发送出去了。如果在之前设置了跟响应体相关的报头,而又在 body_filter_by_lua 中修改了响应体,会导致响应报头和实际响应的不一致。举个简就是说这个例子里上游的服务器返回了 Content-Length 报头,而 body_filter_by_lua* 又修改了响应体的实际大小(因为我删除了一些字符串)。客户端收到这个报头后,按其中的 Content-Length 去处理,顺着一头栽进坑里。由于Nginx 的流式响应,发出去的报头就像泼出去的水,要想修改只能提前进行。这是流式处理常常面对的悖论:要在流的开始输出长度,但又不能在那个时间事先知道流的长度。 对于处理逻辑简单的场景来说,Lua是十分合适的。
第二种处理办法 lua脚本:
1
2
3
4
5
6
ngx.req.read_body()
local data = ngx.req.get_body_data()
local args = ngx.req.get_uri_args()
local res = ngx.location.capture("/bh-scenes-1.2.min.css", {method = ngx.HTTP_GET, body=data, args=args})
res.body = string.gsub(res.body, "max%-width%:1632px%;", "")
ngx.say(res.body)
Nginx相关配置:
1
2
3
4
5
6
7
8
9
10
11
location = /bh-scenes-1.2.min.css {
root /usr/local/nginx/nginx/html/fe_components/jqwidget/teal/;
}
location /fe_components/ {
location ~ /fe_components/jqwidget/.*/bh-scenes-1.2.min.css {
content_by_lua_file /opt/lua/replace2.lua;
}
root /usr/local/nginx/nginx/html;
}
第四种处理办法 需要重新编译安装OpenResty,编译时加入参数 –with-http_sub_module。 Nginx相关配置如下:
1
2
3
4
5
6
7
8
9
10
location /fe_components/ {
location ~ /jqwidget/teal/bh-scenes-1.2.min.css {
#sub_filter_types *;
sub_filter_types text/css;
sub_filter 'max-width:1632px;' '';
sub_filter_once off;
root /usr/local/nginx/nginx/html/;
}
root /usr/local/nginx/nginx/html;
}