Lua-Nginx-Module常用指令(中)
九、控制响应头
HTTP响应头需要配置很多重要的信息,例如添加CDN缓存时间、操作set-cookie、标记业务数据类型等。利用Lua的API可以轻松完成这些配置,并且它有丰富的模块可供选择。 9.1 获取响应头 ngx.resp.get_headers 含义:读取当前请求的响应头,并返回一个Lua的table类型的数据。 示例: server { listen 80; server_name testnginx.com; location / { content_by_lua_block { local ngx = require "ngx"; local h = ngx.resp.get_headers() for k,v in pairs(h) do ngx.say(‘Header name: ‘,k,‘ value: ‘,v) end --因为是table,所以可以使用下面的方式读取单个响应头的值 ngx.say(h["content-type"]) } } } 执行结果如下: # curl -i ‘ttp://testnginx.com/test?=12132&a=2&b=c&dd‘ HTTP/1.1 200 OK Server: nginx/1.12.2 Date: Fri,08 Jun 2018 07:36:35 GMT Content-Type: application/octet-stream Transfer-Encoding: chunked Connection: keep-alive Header name:content-type value: application/octet-stream Header name:connection value: keep-alive application/octet-stream 9.2 修改响应头 语法:value = ngx.header.HEADER 配置环境:rewrite_by_lua,access_by_lua,content_by_lua,header_filter_by_lua,body_filter_by_lua,log_by_lua 含义:对响应头进行修改、清除、添加等操作。 server { listen 80; server_name testnginx.com; location / { content_by_lua_block { local ngx = require "ngx" ngx.header.content_type = ‘text/plain‘; --在代码里面是下划线,输出时就变成中横线了 ngx.header.Test_Nginx = ‘Lua‘; --下面的代码等同于ngx.header.A_Ver = ‘aaa‘ ngx.header["A_Ver"] = ‘aaa‘; --读取响应头,并赋值给变量a local a = ngx.header.Test_Nginx; } } } 执行代码,下划线都被替换成了中横线,如下所示: # curl -i ‘http://testnginx.com/?test=12132&a=2&b=c&dd‘ HTTP/1.1 200 OK Server: nginx/1.12.2 Date: Fri,08 Jun 2018 03:18:16 GMT Content-Type: application/octet-stream Transfer-Encoding: chunked Connection: keep-alive test-type: ttt Test-Nginx: Lua A-Ver: aaa 有时需要在一个响应头中存放多个值,例如,当访问/test 路径时,需要为set-cookie设置两个Cookie: location = /test { content_by_lua_block { local ngx = require "ngx" --以逗号分隔两个Cookie ngx.header[‘Set-Cookie‘] = {‘test1=1; path=/test‘,‘test2=2; path=/test‘} } } 输出结果如下: # curl -i ‘http://testnginx.com/test?=12132&a=2&b=c&dd‘ HTTP/1.1 200 OK Server: nginx/1.12.2 Date: Fri,08 Jun 2018 03:21:59 GMT Content-Type: application/octet-stream Transfer-Encoding: chunked Connection: keep-alive Set-Cookie: test1=1; path=/test Set-Cookie: test2=2; path=/test 9.3 清除响应头 ngx.header["X-Test"] = nil; 十、读取请求体$request_body表示请求体被读取到内存中的数据,一般由proxy_pass、fastcgi_pass、uwsgi_pass和scgi_pass等指令进行处理。由于Nginx默认不读取请求体的数据,所以当Lua通过ngx.var.request_body的方式获取请求体时会发现数据为空。那么,该如何获得请求体的数据呢?下面将介绍几种可行的方式。 10.1 强制获取请求体 默认:off 配置环境:http,server,location,location if 含义:默认为off,即不读取请求体。如果设置为on,则表示强制读取请求体,此时,可以通过ngx.var.request_body来获取请求体的数据。但需要注意一种情况,$request_body存在于内存中,如果它的字节大小超过Nginx配置的client_body_buffer_size的值,Nginx就会把请求体存放到临时文件中,此时数据就不在内存中了,这会导致$request_body为空,所以需要设置client_body_buffer_size和client_max_body_size的值相同,避免出现这种情况。 10.2 用同步非阻塞方式获取请求体 环境:rewrite_by_lua,access_by_lua,content_by_lua* 含义:同步读取客户端请求体,且不会阻塞Nginx的事件循环。使用此指令后,就可以通过ngx.req.get_body_data来获取请求体的数据了。但如果是使用临时文件来存放请求体的话,就需要先使用函数ngx.req.get_body_file来获取临时文件名,再去读取临时文件中的请求体数据了。 ngx.req.get_body_data ngx.req.get_post_args ngx.req.get_body_file 10.3 使用场景示例 下面将对这些指令的使用方式和使用场景进行展示。 server { listen 80; server_name testnginx.com; location / { client_max_body_size 10k; client_body_buffer_size 1k; content_by_lua_block { local ngx = require "ngx" --开启读取请求体模式 ngx.req.read_body() --获取内存中的请求体 local data = ngx.req.get_body_data() if data then ngx.print(‘ngx.req.get_body_data: ‘,data,‘ ---- type is ‘,type(data)) return else --如果没有获取到内存中的请求体数据,则去临时文件中读取 local file = ngx.req.get_body_file() if file then ngx.say("body is in file ",file) else ngx.say("no body found") end end } } 配置好后,重载Nginx配置(重载是指使用HUP信号或reload命令来重新加载配置),先用一个小于1KB的请求体(在Nginx配置中设置client_body_buffer_size为1k)执行请求,输出的是string字符串类型,如下所示: # curl -i http://testnginx.com/ -d ‘test=12132&a=2&b=c&dd‘ HTTP/1.1 200 OK Server: nginx/1.12.2 Date: Wed,06 Jun 2018 11:03:35 GMT Content-Type: application/octet-stream Transfer-Encoding: chunked Connection: keep-alive ngx.req.get_body_data: test=12132&a=2&b=c&dd ---- type is string 获取table类型的请求体 server { listen 80; server_name testnginx.com; location / { client_max_body_size 10k; client_body_buffer_size 1k; content_by_lua_block { --开启读取请求体模式 ngx.req.read_body() -- 获取内存中的请求体,返回的结果是Lua的table类型的数据 local args,err = ngx.req.get_post_args() if args then for k,v in pairs(args) do if type(v) == "table" then --如果存在相同的参数名,就会将相同的参数并列在一起,以逗号分隔 ngx.say(k,": ",table.concat(v,",")) else ngx.say(k,v) end end else --如果没有获取到内存中的请求体数据,则去临时文件中读取 local file = ngx.req.get_body_file() if file then ngx.say("body is in file ",file) else ngx.say("no body found") end end } } } 发送测试请求,其中a参数有2个,c参数值为空,d参数连等号都没有。执行结果如下所示: # curl -i http://testnginx.com/ -d ‘test=12132&a=2&b=c&dd=1&a=354&c=&d‘ b: c dd: 1 d: true c: test: 12132 a: 2,354 可以看到参数a的两个值并列显示,并以逗号分隔,参数c显示为空,参数d的结果为布尔值true。 # curl -i http://testnginx.com/ -d ‘test=12132&a=2&b=kls204120312saldkk12 easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2je204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2je204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2je204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jesk20312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2je204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej11‘ HTTP/1.1 100 Continue HTTP/1.1 200 OK Server: nginx/1.12.2 Date: Wed,06 Jun 2018 10:14:32 GMT Content-Type: application/octet-stream Transfer-Encoding: chunked Connection: keep-alive body is in file /usr/local/nginx_1.12.2/client_body_temp/0000000051 因为请求体数据的大小大于client_body_buffer_size的值,所以使用了临时文件存储请求体的数据。因此,需要先获取存放数据的临时文件名,再去读取请求体数据。
10.4 使用建议 十一、输出响应体在Lua中,响应体的输出可以使用ngx.print 和 ngx.say 这两个指令完成。 11.1 异步发送响应体 location / { content_by_lua_block { local ngx = require "ngx"; local h = ngx.req.get_headers() for k,v in pairs(h) do ngx.print(‘Header name: ‘,v) end } } 执行结果如下(所有的数据会合并到一起进行发送): # curl -i ‘http://testnginx.com/test?=12132&a=2&b=c&dd‘ HTTP/1.1 200 OK Server: nginx/1.12.2 Date: Fri,08 Jun 2018 08:11:40 GMT Content-Type: application/octet-stream Transfer-Encoding: chunked Connection: keep-alive Header name:host value: testnginx.comHeader name:accept value: */*Header name:user-agent value: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4. ngx.say 11.2 同步发送响应体 ngx.flush server { listen 80; server_name testnginx.com; default_type ‘text/plain‘; location /test1 { content_by_lua_block { ngx.say("test ") ngx.say("nginx ") ngx.sleep(3) ngx.say("ok!") ngx.say("666!") } } location /test2 { content_by_lua_block { ngx.say("test ") ngx.say("nginx ") ngx.flush(true) ngx.sleep(3) ngx.say("ok!") ngx.say("666!") } } } 访问/test1 和 /test2后,从执行结果可以看出,带有ngx.flush(true) 指令的内容会先输出test nginx,然后,等待大约3秒后再输出ok! 666!。如果没有配置ngx.flush(true)指令,请求会在等待3秒后输出完整的一句话。 十二、正则表达式虽然Lua支持正则匹配且功能齐全,但在Nginx上推荐使用Lua-lua提供的指令。 ngx.re.match location / { content_by_lua_block { local ngx = require "ngx"; --匹配多个数字+aaa的正则表达式 local m,err = ngx.re.match(ngx.var.uri,"([0-9]+)(aaa)"); if m then --匹配成功后输出的信息 ngx.say(ngx.var.uri,‘---match success---‘,‘its type: ‘,type(m)) ngx.say(ngx.var.uri,‘---m[0]--- ‘,m[0]) ngx.say(ngx.var.uri,‘---m[1]--- ‘,m[1]) ngx.say(ngx.var.uri,‘---m[2]--- ‘,m[2]) else if err then ngx.log(ngx.ERR,"error: ",err) return end ngx.say("match not found") end } } 执行结果如下: # curl ‘http://testnginx.com/test/a123aaa/b456aaa/c‘ /test/a123aaa/b456aaa/c---match success---its type: table /test/a123aaa/b456aaa/c---m[0]---123aaa /test/a123aaa/b456aaa/c---m[1]---123 /test/a123aaa/b456aaa/c---m[2]---aaa 从执行结果可以看出: 12.2 全部捕获 location / { content_by_lua_block { local ngx = require "ngx"; --参数i表示忽略大小写 local m_table,err = ngx.re.gmatch(ngx.var.uri,"([0-9]+)(aaa)","i"); if not m_table then ngx.log(ngx.ERR,err) return end while true do local m,err = m_table() if err then ngx.log(ngx.ERR,err) return end if not m then break end ngx.say(m[0]) ngx.say(m[1]) end } } 执行结果如下: # curl ‘http://testnginx.com/test/a123aaa/b456AAA/c‘ 123aaa 123 456AAA 456 ngx.re.match和ngx.re.gmatch都有一个options参数,用来控制匹配的执行方式,options常用参数说明见表7-1。 12.3 更高效的匹配和捕获 ngx.re.match和ngx.re.gmatch在使用过程中都会生成Lua table,如果只需确认正则表达式是否可以匹配成功,推荐使用如下指令。 ngx.re.find location / { content_by_lua_block { local ngx = require "ngx"; local uri = ngx.var.uri --使用o、j两个参数进行匹配,以提升性能 local find_begin,find_end,err = ngx.re.find(uri,"oj"); if find_begin then ngx.say(‘begin: ‘,find_begin) ngx.say(‘end: ‘,find_end) --利用Lua的string.sub函数来获取数据 ngx.say(‘find it: ‘,string.sub(uri,find_begin,find_end)) return end } } 执行结果如下: # curl ‘http://testnginx.com/test/a123aaa/b456AAAa/c‘ begin:8 end:13 find it: 123aaa ngx.re.match、ngx.re.gmatch和 ngx.re.find 都支持ctx参数,有关ctx参数的说明如下。 location / { content_by_lua_block { local ngx = require "ngx"; local uri = ngx.var.uri --从uri位置为10的地方开始进行匹配,下标默认从1开始,只匹配nth是1的数据,即([0-9]+)的值 local ctx = { pos = 10 } local find_begin,"oji",ctx,1); if find_begin then ngx.say(‘begin: ‘,find_end) ngx.say(‘find it: ‘,find_end)) return end } } 执行结果如下: # curl ‘http://testnginx.com/test/a123aaa/b456AAAa/c‘ begin:10 end:10 find it: 3 因为ctx的位置是10,所以uri前面的“/test/a12”这9个字符被忽略了,匹配到的就只有3aaa,又因为nth为1,所以捕获到的值是3。 12.4 替换数据 location / { content_by_lua_block { local ngx = require "ngx"; local uri = ngx.var.uri local n_str,err = ngx.re.sub(uri,"([0-9]+)",‘zzzz‘) if n_str then ngx.say(uri) ngx.say(n_str) ngx.say(n) else ngx.log(ngx.ERR,err) return end } } 执行结果如下: # curl ‘http://testnginx.com/test188/x2/1231‘ /test188/x2/1231 /testzzzz/x2/1231 1 从结果可以看出,只在第一次匹配成功时进行了替换操作,并且只替换了1次,所以n的结果是1。如果要替换匹配到的全部结果可以使用ngx.re.gsub,示例如下: local n_str,err = ngx.re.gsub(uri,‘zzzz‘) 从执行结果可知,替换了3次: # curl ‘http://testnginx.com/test188/x2/1231‘ /test188/x2/1231 /testzzzz/xzzzz/zzzz 3 12.5 转义符号 local find_regex = [[d+]] local m = ngx.re.match("xxx,43",find_regex) ngx.say(m[0]) --输出 43 通常建议使用[[]]的方式。 十三、子请求Nginx一般分两种请求类型,一种是主请求;一种是子请求,即subrequest。主请求从Nginx的外部进行访问,而子请求则在Nginx内部进行访问。子请求不是HTTP请求,不会增加网络开销。它的主要作用是将一个主请求分解为多个子请求,用子请求去访问指定的location服务,最后汇总到一起完成主请求的任务。 13.1 请求方法 表7-2 Lua API常见的请求方法说明 13.2 单一子请求 ngx.location.capture 表7-3 res的元素名及其用途 ngx.location.capture的第2个参数options是可选参数,也可以包含多个参数,示例如下: server { listen 80; server_name testnginx.com; default_type ‘text/plain‘; location = /main { set $m ‘hello‘; content_by_lua_block { local ngx = require "ngx"; --发起子请求,访问/test,请求方式是GET,请求体是test nginx,子请求的URL参数是a=1&b=2,并使用copy_all_vars将主请求的Nginx变量($m)全部复制到子请求中 local res = ngx.location.capture( ‘/test‘,{ method = ngx.HTTP_GET,body = ‘test nginx‘,args = { a = 1,b = 2 },copy_all_vars = true } ) ngx.say(res.status) ngx.say(res.body) ngx.say(type(res.header)) ngx.say(type(res.truncated)) } } location = /test { #只能在Nginx内部进行访问 internal; content_by_lua_block { local ngx = require "ngx"; --获取请求体,在这里是获取主请求的请求体 ngx.req.read_body() local body_args = ngx.req.get_body_data() --输出请求的参数,获取主请求的m变量的值,并与world进行字符串拼接 ngx.print(‘request_body: ‘,body_args,‘ capture_args: ‘,ngx.var.args,‘--- copy_all_vars : ‘,ngx.var.m .. ‘world! ‘) } } } 执行结果如下: # curl ‘http://testnginx.com/main‘ 200 request_body:test nginx capture_args:a=1&b=2--- copy_all_vars : helloworld! table boolean 从示例中可以看出: 1.ngx.location.capture的第2个参数options可以包含多个table类型的参数。 2.子请求的请求方法由参数method进行配置,示例中的请求方法为GET。 3.子请求通过参数body可以定义新的请求体。 4.子请求通过参数args可以配置新的URL的args,args是table类型的。 5.copy_all_vars = true的作用是将主请求的全部变量传递给子请求,如果没有此配置就不会传递过去。 6.从子请求的返回结果可以获取状态码、响应体、响应头、结果是否被截断。 1.vars参数,table类型,可以设置子请求中的变量值,前提是该变量在Nginx中被声明过。如果配置copy_all_vars = true,且vars里有和主请求相同的变量,则会使用vars中变量的值;如果vars里是新变量,就会和主请求的变量一起传递过去。 2.share_all_vars参数,用来共享主请求和子请求的变量,如果在子请求中修改了共享变量的值,主请求的变量值也会被改变。不推荐使用此参数,因为可能会导致很多意外问题的出现。 3.always_forward_body参数,默认值为false,此时,如果不设置body参数,且请求方法是PUT或POST,则主请求的请求体可以传给子请求。如果把always_forward_body设置为 true,且不设置body参数,无论请求方法是什么,主请求的请求体都会传给子请求。 4.ctx参数,指定一个table作为子请求的ngx.ctx表,它可以使主请求和子请求共享请求头的上下文环境。 关于参数vars的使用方式,示例如下: location = /main { set $m ‘hello‘; set $mm ‘‘; content_by_lua_block { local ngx = require "ngx"; local res = ngx.location.capture( ‘/test‘,{ method = ngx.HTTP_POST,vars = {mm = ‘MMMMM‘,m = ‘hhhh‘}} ) ngx.say(res.body) } } location = /test { content_by_lua_block { local ngx = require "ngx"; ngx.print(ngx.var.m .. ngx.var.mm ) } } 执行结果如下: # curl ‘http://testnginx.com/main‘ hhhhMMMMM 主请求的变量在子请求中被修改了,并传给了子请求指定的/test: 13.3 并发子请求 有时需要发送多条子请求去获取信息,这时,就要用到并发操作了。 ngx.location.capture_multi server { listen 80; server_name testnginx.com; default_type ‘text/plain‘; location = /main { set $m ‘hello‘; set $mm ‘‘; content_by_lua_block { local ngx = require "ngx"; --发送两个子请求,会返回两个结果集 local res1,res2 = ngx.location.capture_multi{ { "/test1?a=1&b=2" },{ "/test2",{ method = ngx.HTTP_POST},body = "test nginx" },} --返回的body的方式和ngx.location.capture一样 if res1.status == ngx.HTTP_OK then ngx.say(res1.body) end if res2.status == ngx.HTTP_OK then ngx.say(res2.body) end } } location = /test1 { echo ‘test1‘; } location = /test2 { echo ‘test2‘; } } 执行结果如下: # curl ‘http://testnginx.com/main‘ test1 test2 主请求需要等到所有的子请求都返回后才会结束子请求的执行,最慢的子请求的执行时间就是整体的消耗时间,所以在实际业务中需要对子请求的超时时间做好限制。注意:Nginx对子请求有并发数量限制,目前Nginx 1.1以上的版本限制子请求并发数量为200个,老版本是50个。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |