正文  软件开发 > html5 >

【技术干货】听阿里云CDN安防技术专家金九讲tengine+lua开发

【技术干货】听阿里云CDN安防技术专家金九讲tengine+lua开发一、介绍 二、安装 三、运行 四、开发 1.介绍Tengine:轻量级、高性能、高并发、配置化、模块化、可扩展、可移植的Web...

【技术干货】听阿里云CDN安防技术专家金九讲tengine+lua开发


一、介绍

二、安装

三、运行

四、开发

1.介绍


Tengine:轻量级、高性能、高并发、配置化、模块化、可扩展、可移植的Web和反向代理 服务器,Tengine是nginx超集,但做了很多优化,包含了很多比较有用的模块,比如直接包含了lua、proc等很有用的模块。


Lua:一个很轻量级的 脚本,也号称性能最高的 脚本。代码总共不到600k,32个C文件,23个头文件:

root@j9~/lua-5.1.5/src#du-sh./572K./root@j9~/lua-5.1.5/src#ls*.c|wc-l32root@j9~/lua-5.1.5/src#ls*.h|wc-l23root@j9~/lua-5.1.5/src#


可以非常容易的嵌入C和C++工程中,也比较容易与C和C++互动,这也是目前Lua主要的用法。

ngx_lua:一个nginx很重要的第三方模块,作者:章亦春(agentzh、春哥),结合了nginx和Lua各自优点,把Lua嵌入nginx中,使其支持Lua来快速开发基于nginx下的业务逻辑。

[https://github.com/openresty/lua-nginx-module](https://github.com/openresty/lua-nginx-module)



2. 安装


2.1、LuaJIT



wget-chttp://luajit.org/download/LuaJIT-2.0.4.tar.gztarxzvfLuaJIT-2.0.4.tar.gzcdLuaJIT-2.0.4makeinstallPREFIX=/usr/local/luajit#注意环境变量!exportLUAJIT_LIB=/usr/local/luajit/libexportLUAJIT_INC=/usr/local/luajit/include/luajit-2.0



2.2、Tengine


tengine最新代码中已经包含lua模块了,直接git clone下来就可以


gitclonehttps://github.com/alibaba/tengine.gitcdtengine./configure--prefix=/opt/tengine--with-http_lua_modulemakemakeinstall




如果是原生nginx的话,得自行下载lua模块代码:

wgethttp://nginx.org/download/nginx-1.7.8.tar.gztarxvfnginx-1.7.8.tar.gzcdnginx-1.7.8mkdirmodulescdmodulesgitclonehttps://github.com/openresty/lua-nginx-module.gitcd.../configure--prefix=/opt/nginx--add-module=./modules/lua-nginx-module/makemakeinstall


3. 运行


修改/opt/tengine/conf/nginx.conf:

worker_processes1;error_loglogs/error.log;pidlogs/nginx.pid;events{worker_connections1024;}http{includemime.types;default_typeapplication/octet-stream;log_formatmain‘$remote_addr-$remote_user[$time_local]"$request"‘‘$status$body_bytes_sent"$http_referer"‘‘"$http_user_agent""$http_x_forwarded_for"‘;access_loglogs/access.logmain;sendfileon;keepalive_timeout65;server{listen80;server_namelocalhost;location/{roothtml;indexindex.htmlindex.htm;}location/hello_lua{content_by_lua‘ngx.say("Lua:helloworld!")‘;}}}


运行tengine:

root@j9~/tengine#/opt/tengine/sbin/nginx

curl访问一下hello_lua:

root@j9~/tengine#curlhttp://localhost/hello_luaLua:helloworld!


运行ok。



4、开发


> 语法

> 入门

> 深入




4.1、语法


参考:

[Lua简明教程](http://coolshell.cn/articles/10739.html)

[Lua在线lua学习教程](http://book.luaer.cn/)


4.2、入门


4.2.1、API


- ngx.print

输出响应内容体;

例如:ngx.print("a", "b", "c")



- ngx.say

跟ngx.print的区别只是最后会多输出一个换行符;

例如:ngx.say("a", "b", "c")


- ngx.status

设置响应HTTP状态码;

<u>注意,设置状态码仅在响应头发送前有效。当调用ngx.say或者ngx.print时自动发送响应状态码(默认为200);可以通ngx.headers_sent来判断是否发送了响应状态码。</u>

例如:ngx.status = 200


- ngx.exit

设置响应HTTP状态码并退出;

<u>注意,设置状态码仅在响应头发送前有效,并且该函数调用之后该函数后面的lua将被忽略掉,因为已经exit了。</u>

例如:ngx.exit(200)


- ngx.header

输出响应头;

<u>注意,头部字段中含有横杠(-)的要转换成下划线(_),ngx_lua模块自动将_转换成-。</u>

例如:ngx.header["X-Cache"] = "HIT" 或者 ngx.header.X_Cache = "HIT"或者ngx.header.X_Cache = {"AA", "BB"}


- ngx.redirect

301或者302重定向

例如:ngx.redirect("[http://www.taobao.org](http://www.taobao.org)", 301)


- ngx.log

打印nginx错误日志,日志级别有:ngx.STDERR、ngx.EMERG、ngx.ALERT、ngx.CRIT、ngx.ERR、ngx.WARN、ngx.NOTICE、ngx.INFO、ngx.DEBUG

例如:ngx.log(ngx.ERR, "test: ", "ok")


例子:

server{listen9898;location/{default_type"text/html";content_by_lua‘localheaders_sent_1=ngx.headers_sentngx.header["X-Cache"]="HIT"ngx.header.Y_Cache="MISS"ngx.header.Z_Cache={"AA","BB"}ngx.status=602localheaders_sent_2=ngx.headers_sentngx.print("a","b")localheaders_sent_3=ngx.headers_sentngx.say("c","d")ngx.say("e","f")ngx.say("headers_sent_1:",tostring(headers_sent_1))ngx.say("headers_sent_2:",tostring(headers_sent_2))ngx.say("headers_sent_3:",tostring(headers_sent_3))ngx.log(ngx.ERR,"ngx.logtestok")ngx.exit(601)ngx.say("g","h")‘;}location^~/redirect{content_by_lua‘ngx.redirect("http://www.taobao.org",301)‘;}}




测试结果:


root@j9~#curl"http://127.0.0.1:9898/"-iHTTP/1.1602Server:Tengine/2.2.0Date:Mon,19Oct201516:10:42GMTContent-Type:text/htmlTransfer-Encoding:chunkedConnection:keep-aliveX-Cache:HITY-Cache:MISSZ-Cache:AAZ-Cache:BBabcdefheaders_sent_1:falseheaders_sent_2:falseheaders_sent_3:trueroot@j9~#curl"http://127.0.0.1:9898/redirect"-iHTTP/1.1301MovedPermanentlyServer:Tengine/2.2.0Date:Mon,19Oct201516:18:16GMTContent-Type:text/htmlContent-Length:284Connection:keep-aliveLocation:http://www.taobao.org<!DOCTYPEHTMLPUBLIC"-//IETF//DTDHTML2.0//EN"><html><head><title>301MovedPermanently</title></head><bodybgcolor="white"><h1>301MovedPermanently</h1><p>TherequestedresourcehasbeenassignedanewpermanentURI.<hr/>PoweredbyTengine/2.2.0</body></html>root@j9~#


- ngx.var

读取nginx变量,如nginx变量为$a,则在Lua中通过ngx.var.a获取,也可以给nginx变量赋值如ngx.var.a = "aa",前提是该变量在nginx中必须存在,不能在Lua中创建nginx变量。另外,对于nginx location中使用正则捕获的捕获组可以使用ngx.var[捕获组数字]获取。



例子


server{listen9898;location~/var/([^/]*)/([^/]*){default_type"text/html";set$a"aaa";set$b$host;content_by_lua‘ngx.say("$a:",ngx.var.a)ngx.say("$b:",ngx.var.b)ngx.say("$host:",ngx.var.host)ngx.say("$arg_id:",ngx.var.arg_id)ngx.say("$1:",ngx.var[1])ngx.say("$2:",ngx.var[2])‘;}}



测试结果:

root@j9~#curl"http://127.0.0.1:9898/var/aaaa/bbbb?id=22"-H"Host:www.taobao.org"$a:aaa$b:www.taobao.org$host:www.taobao.org$arg_id:22$1:aaaa$2:bbbbroot@j9~#



- ngx.req.raw_header

未解析的请求头字符串;

例如:ngx.req.raw_header()


- ngx.req.get_headers

获取请求头,默认只获取前100个头部,如果想要获取所有头部可以调用ngx.req.get_headers(0);获取带中划线的请求头时要把中划线转换成下划线使用如headers.user_agent这种方式;如果一个请求头有多个值,则返回的是table;

例如:ngx.req.get_headers()


- ngx.req.get_uri_args

获取url请求参数,其用法与ngx.req.get_headers类似;


- ngx.req.get_post_args

获取post请求body参数,其用法与ngx.req.get_uri_args类似,但必须提前调用ngx.req.read_body();


- ngx.req.read_body

如果要获取请求的body,则需要调用ngx.req.read_body(),否则获取不到body数据,(ps:也可以在nginx配置文件中加入指令lua_need_request_body on;来开启读取body,但官方不推荐)


- ngx.req.discard_body

忽略请求的body

注意,如果处理一个包含body的请求且需要ngx.exit时,需要调用此函数来忽略body,否则nginx可能将body当成header来解析,从而导致400错误;


- ngx.req.get_body_data

获取请求body数据


例子

location^~/req{content_by_lua‘ngx.say("===========ngx.req.raw_header=")ngx.say(ngx.req.raw_header())localheaders=ngx.req.get_headers()ngx.say("===========headers============")ngx.say("Host:",headers["Host"])ngx.say("user-agent:",headers.user_agent)ngx.say("===========allheaders========")fork,vinpairs(headers)doiftype(v)=="table"thenngx.say(k,":",table.concat(v,","))elsengx.say(k,":",v)endendngx.say("===========args===============")localargs=ngx.req.get_uri_args()fork,vinpairs(args)dongx.say(k,":",v)endngx.say("===========body===============")ngx.say("bodydata:",ngx.req.get_body_data())ngx.req.read_body()localpost_args=ngx.req.get_post_args()fork,vinpairs(post_args)dongx.say(k,":",v)endngx.say("bodydata:",ngx.req.get_body_data())‘;}



测试结果:


root@j9~#curl"http://127.0.0.1:9898/req?a=11&b=22&c=33"--data"d=11&e=22&f=33"===========ngx.req.raw_header=POST/req?a=11&b=22&c=33HTTP/1.1User-Agent:curl/7.22.0(x86_64-pc-linux-gnu)libcurl/7.22.0OpenSSL/1.0.1zlib/1.2.3.4libidn/1.23librtmp/2.3Host:127.0.0.1:9898Accept:*/*Content-Length:14Content-Type:application/x-www-form-urlencoded===========headers============Host:127.0.0.1:9898user-agent:curl/7.22.0(x86_64-pc-linux-gnu)libcurl/7.22.0OpenSSL/1.0.1zlib/1.2.3.4libidn/1.23librtmp/2.3===========allheaders========host:127.0.0.1:9898content-type:application/x-www-form-urlencodedaccept:*/*content-length:14user-agent:curl/7.22.0(x86_64-pc-linux-gnu)libcurl/7.22.0OpenSSL/1.0.1zlib/1.2.3.4libidn/1.23librtmp/2.3===========args===============b:22a:11c:33===========body===============bodydata:nild:11f:33e:22bodydata:d=11&e=22&f=33root@j9~#



- ngx.escape_uri/ngx.unescape_uri

uri编码解码


- ngx.encode_args/ngx.decode_args

参数编码解码


- ngx.encode_base64/ngx.decode_base64

BASE64编码解码


- ngx.md5

md5加密



例子


location^~/code{content_by_lua‘localrequest_uri=ngx.var.request_urilocalargs={a=11,b=22}ngx.say("requesturi:",request_uri)ngx.say("unescaperequesturi:",ngx.unescape_uri(request_uri))ngx.say("encodeargs:",ngx.encode_args(args))ngx.say("encodebase64requesturi:",ngx.encode_base64(request_uri))ngx.say("md5(123456):",ngx.md5("123456"))‘;}

测试结果:


root@j9~#curl"http://127.0.0.1:9898/code?name=%E9%87%91%E4%B9%9D"requesturi:/code?name=%E9%87%91%E4%B9%9Dunescaperequesturi:/code?name=金九encodeargs:a=11&b=22encodebase64requesturi:L2NvZGU/bmFtZT0lRTklODclOTElRTQlQjklOUQ=md5(123456):e10adc3949ba59abbe56e057f20f883eroot@j9~#





- ngx.shared.DICT


共享内存接口,其中DICT为共享内存zone名称,在nginx.conf中通过指令lua_shared_dict配置,而且lua_shared_dict指令配置的共享内存大小最小值为8k。


例子

lua_shared_dictcc_shared_data16k;server{listen9999;default_type"text/html";location^~/shared_data{content_by_lua‘localshared_data=ngx.shared.cc_shared_datalocali=shared_data:get("i")ifnotithenshared_data:set("i",1)endi=shared_data:incr("i",1)ngx.say("i:",i)‘;}}


测试结果

root@j9~#curl"http://127.0.0.1:9999/shared_data"i:2root@j9~#curl"http://127.0.0.1:9999/shared_data"i:3root@j9~#


ngx.shared.DICT详细说明:[http://wiki.nginx.org/HttpLuaModule#ngx.shared.DICT](http://wiki.nginx.org/HttpLuaModule#ngx.shared.DICT)



**4.2.2、指令**


指令阶段范围说明
init_by_lua/init_by_lua_fileloading-confighttpnginx master进程加载配置时执行;通常用于初始化全局配置/预加载Lua模块
init_worker_by_lua/init_worker_by_lua_filestarting-workerhttp每个nginx worker进程启动时调用的计时器,如果master进程不允许则只会在init_by_lua之后调用;通常用于定时拉取配置/数据,或者后端服务的健康检查
set_by_lua/set_by_lua_filerewriteserver,server if,location,location if设置nginx变量,可以实现复杂的赋值逻辑;此处是阻塞的,Lua代码要做到非常快
rewrite_by_lua/rewrite_by_lua_filerewrite tailhttp,server,location,location ifrewrite阶段处理,可以实现复杂的转发/重定向逻辑
access_by_lua/access_by_lua_fileaccess tailhttp,server,location,location if请求访问阶段处理,用于访问控制
content_by_lua/content_by_lua_filecontentlocation,location if内容处理器,接收请求处理并输出响应
header_filter_by_lua/header_filter_by_lua_fileoutput-header-filterhttp,server,location,location if设置header和cookie
body_filter_by_lua/body_filter_by_lua_fileoutput-body-filterhttp,server,location,location if对响应数据进行过滤,比如截断、替换
log_by_lua/log_by_lua_fileloghttp,server,location,location iflog阶段处理,比如记录访问量/统计平均响应时间


更详细的解释请参考官网:http://wiki.nginx.org/HttpLuaModule#Directives



init_by_lua

每次nginx重新加载配置时执行,可以用它来完成一些耗时模块的加载,或者初始化一些全局配置;



例子:

init_by_lua‘cjson=require("cjson")ngx.log(ngx.ERR,"init_by_luaok")‘;server{listen9292;default_type"text/html";location/{content_by_lua‘localarg_json=cjson.decode(ngx.var.arg_json)ngx.say("aa:",arg_json.aa)‘;}}



测试结果:

root@j9~#curl‘http://127.0.0.1:9292/?json={"aa":111,"bbb":222}‘aa:111root@j9~#




**init_worker_by_lua**


每个worker启动之后初始化时执行,通常用于每个worker都要做的工作,比如启动定时任务



例子:


worker_processes2;http{#这里省略了其他配置init_worker_by_lua‘ngx.log(ngx.ERR,"testinit_worker_by_lua")--TODO:启动定时任务‘;}



grep一下error.log,会发现两条包含"test init_worker_by_lua"关键字的log,说明每个worker都会执行这个Lua代码。


set_by_lua

语法:set_by_lua resluascriptstr


arg1 $arg2...; 在Lua代码中可以实现所有复杂的逻辑,但是要执行速度很快,不要阻塞;

需要注意的是,这个指令需要加入模块ngx_devel_kit,否则不支持这个指令。


这个指令的Lua代码中不支持以下API:

1、输出(ngx.say、ngx.send_headers……)

2、控制(ngx.exit……)

3、子请求(ngx.location.capture、ngx.location.capture_multi……)

4、cosocket(ngx.socket.tcp、ngx.req.socket……)

5、ngx.sleep


例子:

server{listen9393;default_type"text/html";location/add{set$diff‘‘;set$double_c‘‘;set_by_lua$sum‘locala=ngx.var.arg_alocalb=ngx.var.arg_bngx.var.diff=a-bngx.var.double_c=2*tonumber(ngx.arg[1])returna+b;‘$arg_c;return200"a+b=$sum,a-b=$diff,2*c=$double_c";}}




测试结果:


root@j9~#curl"http://127.0.0.1:9393/add?a=11&b=22&c=88"a+b=33,a-b=-11,2*c=176



rewrite_by_lua

执行内部URL重写或者外部重定向(301或者302),典型的如伪静态化的URL重写。其默认执行在rewrite处理阶段的最后。


<u>需要注意的是:

1、在长连接中如果调用了ngx.exit(200)一个请求,则需要调用ngx.req.discard_body(),否则nginx可能会把当前请求的body当成header解析,从而导致400错误返回码并且长连接被关闭。

2、如果该阶段调用了ngx.exit(ngx.OK),content_by_lua阶段仍然能得到执行。</u>


例子:


server{listen9494;default_type"text/html";location/rewrite_by_lua{set$a11;rewrite_by_lua‘ngx.var.a="aa"ifngx.var.arg_exit=="ok"thenngx.exit(ngx.OK)elsengx.exit(200)end‘;content_by_lua‘ngx.say("a:",ngx.var.a)‘;}}



测试结果

root@j9~#curl"http://127.0.0.1:9494/rewrite_by_lua?exit=ok"a:aaroot@j9~#curl"http://127.0.0.1:9494/rewrite_by_lua?exit=200"root@j9~#access_by_lua




用于访问控制,比如IP黑白名单限制、鉴权。


例子:


server{listen9595;default_type"text/html";location/{access_by_lua‘localauth=ngx.var.arg_auth;localkey="alicdnj9";ifngx.md5(key)~=auththenreturnngx.exit(403)end‘;content_by_lua‘ngx.say("accessok")‘;}}



测试结果:

root@j9~#curl"http://127.0.0.1:9595/?auth=xx"<!DOCTYPEHTMLPUBLIC"-//IETF//DTDHTML2.0//EN"><html><head><title>403Forbidden</title></head><bodybgcolor="white"><h1>403Forbidden</h1><p>Youdon‘thavepermissiontoaccesstheURLonthisserver.Sorryfortheinconvenience.<br/>Pleasereportthismessageandincludethefollowinginformationtous.<br/>Thankyouverymuch!<table><td>URL:<td>http://127.0.0.1:9595/?auth=xx<td>Server:<td>j9<td>Date:<td>2015/10/2716:47:20<hr/>PoweredbyTengine/2.2.0</body></html>root@j9~#echo-nalicdnj9|md5sum50652c84270d22210593318f5d3016a1-root@j9~#curl"http://127.0.0.1:9595/?auth=50652c84270d22210593318f5d3016a1"accessokroot@j9~#


<u>注意,如果在access_by_lua中调用ngx.exit(ngx.OK),content阶段仍然能得到执行。</u>


content_by_lua

content阶段,<u>注意在同一个Location中不要和其他content阶段指令一起使用,比如proxy_pass。</u>

例子:略


header_filter_by_lua和body_filter_by_lua

分别为header_filter阶段和body_filter阶段,其中body_filter可能会被执行多次。

不支持以下API:


1. 输出 (ngx.say、ngx.send_headers)

2. 控制 (ngx.exit、ngx.exec)

3. 子请求 (ngx.location.capture、ngx.location.capture_multi)

4. Cosocket (ngx.socket.tcp、ngx.req.socket).



比如对后端chunked长度做限制:


server{listen9696;default_type"text/html";set$content_len0;location/{header_filter_by_lua‘--先去掉Content-Length头部,转成Chunked传输ngx.header.content_length=nil‘;body_filter_by_lua‘localcontent_length=#ngx.arg[1]content_length=ngx.var.content_len+content_lengthngx.var.content_len=content_length--最多只能传输10字节的body,否则直接关掉连接ifcontent_length>10thenreturnngx.ERRORend‘;content_by_lua‘fori=1,ngx.var.arg_lendongx.print("a")end‘;}}




测试结果


root@j9~#curl"http://127.0.0.1:9696/?len=10"-iHTTP/1.1200OKServer:Tengine/2.2.0Date:Mon,26Oct201501:48:23GMTContent-Type:text/htmlTransfer-Encoding:chunkedConnection:keep-aliveaaaaaaaaaaroot@j9~#curl"http://127.0.0.1:9696/?len=11"-icurl:(52)Emptyreplyfromserverroot@j9~#



可以看出当参数len为11时,服务器就直接不返回数据了。


**4.3、深入**


1、content_by_lua中的代码一定要注意单引号或者双引号,一般用法是外单内双,或者外双内单。


2、在nginx_lua中值为nil的变量不能与字符串或者数字相加,否则nginx会报500错误。


3、lua调试: ngx.log(ngx.ERR,xx)。(tail -f logs/error.log)


4、*_by_lua_file指令指定的文件支持绝对路径和相对路径,其中相对路径是相对nginx工作目录。


5、lua文件的require函数指定的lua模块路径查找顺序,可以从出错信息中看出来:


no file ‘/opt/libs/lua/a.lua‘

no file ‘./a.lua‘

no file ‘/usr/local/luajit/share/luajit-2.0.4/a.lua‘

no file ‘/usr/local/share/lua/5.1/a.lua‘

no file ‘/usr/local/share/lua/5.1/a/init.lua‘

no file ‘/usr/local/luajit/share/lua/5.1/a.lua‘

no file ‘/usr/local/luajit/share/lua/5.1/a/init.lua‘

no file ‘./a.so‘

no file ‘/usr/local/lib/lua/5.1/a.so‘

no file ‘/usr/local/luajit/lib/lua/5.1/a.so‘

no file ‘/usr/local/lib/lua/5.1/loadall.so‘


其中,第一个/opt/libs/lua/a.lua为lua_package_path指定的路径:lua_package_path ‘/opt/libs/lua/?.lua;;‘;

第二个./a.lua为相对路径,相对于nginx.conf配置文件,而非包含它的lua文件。

so模块查找顺序类似,但是先查找.lua再查找.so,查找.so时先在lua_package_cpah指定的路径查找:lua_package_cpath ‘/opt/libs/lua_shared/?.so;;‘;

可以从出错信息中看出来:


no field package.preload[‘a‘]

no file ‘/opt/libs/lua/a.lua‘

no file ‘./a.lua‘

no file ‘/usr/local/luajit/share/luajit-2.0.4/a.lua‘

no file ‘/usr/local/share/lua/5.1/a.lua‘

no file ‘/usr/local/share/lua/5.1/a/init.lua‘

no file ‘/usr/local/luajit/share/lua/5.1/a.lua‘

no file ‘/usr/local/luajit/share/lua/5.1/a/init.lua‘

no file ‘/opt/libs/lua_shared/a.so‘

no file ‘./a.so‘

no file ‘/usr/local/lib/lua/5.1/a.so‘

no file ‘/usr/local/luajit/lib/lua/5.1/a.so‘

no file ‘/usr/local/lib/lua/5.1/loadall.so‘


6、lua代码一定要健壮,否则不管lua产生什么错,nginx都会返回500错误,这时可以从error.log中查看错误信息来定位。


7、编写lua代码时最好用local局部变量,不要用全局变量。


8、实现worker级别的全局变量:



server{listen9797;default_type"text/html";location/{content_by_lua‘locala=1localb={b=1}localstatus=require("status")ngx.say("a:",a,",b:",b.b,"counter:",status.counter)a=a+1b.b=b.b+1status.counter=(status.counteror0)+1‘;}}



其中status.lua为:


localm={}m.counter=1returnm



测试结果:

root@j9~#curl"http://127.0.0.1:9797/"a:1,b:1counter:1root@j9~#curl"http://127.0.0.1:9797/"a:1,b:1counter:2root@j9~#curl"http://127.0.0.1:9797/"a:1,b:1counter:3root@j9~#



可以看出status.counter的值一直是累加的,这是因为require一个模块只load第一次,后续require该模块都会先看全局表中是否已经load过,load过则就不需要再load了,所以status.counter累加其实是累加m.counter。


**9、定时任务**


API: ok, err = ngx.timer.at(delay, callback, user_arg1, user_arg2, ...)

例子:


localdelay=5localhandlerhandler=function(premature)--dosomeroutinejobinLuajustlikeacronjobifprematurethenreturnendlocalok,err=ngx.timer.at(delay,handler)ifnotokthenngx.log(ngx.ERR,"failedtocreatethetimer:",err)returnendendlocalok,err=ngx.timer.at(delay,handler)ifnotokthenngx.log(ngx.ERR,"failedtocreatethetimer:",err)returnend




<u>注意:在timer处理函数的上下文中不能调用ngx.var.*、ngx.req.*、子请求API、输出API,因为这些API只能在请求上下文中生效。</u>


**10、子请求**


API:res = ngx.location.capture(uri, options?)


上下文:rewrite_by_lua*, access_by_lua*, content_by_lua*


例子:

正向代理,当源站返回301或者302时代理客户端跳转


server{listen8181;default_type"text/html";location/test{content_by_lua‘localres=ngx.location.capture("/get"..ngx.var.request_uri,{method=ngx.HTTP_HEAD})ifres.status==200thenngx.exec("/get"..ngx.var.request_uri)elseifres.status==301orres.status==302thenlocation=res.header["Location"]localm,err=ngx.re.match(location,"http://([^/]+)(/.*)")ifnotmthenngx.exit(500)endhost=m[1]uri=m[2]ngx.exec("/redirect/"..host.."/"..ngx.var.request_uri)elsengx.exit(res.status)end‘;}location~/redirect/([^/]*)/([^/]*){proxy_passhttp://$1/$2?$args;}location/get{if($arg_tag="1"){return302"http://127.0.0.1:8282/$request_uri";}return200"ok";}}server{listen8282;default_type"text/html";location/{return200"redirectok,args:$args";}}



测试结果:

root@j9~#curl"http://127.0.0.1:8181/test?tag=0"-iHTTP/1.1200OKServer:Tengine/2.2.0Date:Mon,26Oct201515:17:10GMTContent-Type:text/htmlContent-Length:2Connection:keep-aliveokroot@j9~#curl"http://127.0.0.1:8181/test?tag=1"-iHTTP/1.1200OKServer:Tengine/2.2.0Date:Mon,26Oct201515:17:14GMTContent-Type:text/htmlContent-Length:19Connection:keep-aliveredirectok,args:tag=1root@j9~#




可见,当传tag为1时,返回的值就是想要的值,不需要再302重定向了。


注意,子请求只能请求本server的非@location。

另外一个需要注意的是,发起子请求之前修改的变量在子请求的location中是获取不到的,这是因为变量的上下文是在请求结构体r中,而子请求是挂在主请求下面,是两个不同的请求。

实验:


server{listen8383;default_type"text/html";set$cc"cc";location/test{content_by_lua‘ngx.var.cc="11"localres=ngx.location.capture("/get"..ngx.var.request_uri)ifres.status==200thenngx.say(res.body)ngx.say("testcc:",ngx.var.cc)elsengx.exit(res.status)end‘;}location/get{content_by_lua‘ngx.say("getcc:",ngx.var.cc)ngx.var.cc="22"‘;}}



结果:


root@j9~#curl"http://127.0.0.1:8383/test"getcc:cctestcc:11root@j9~#




11、location @xx


server{listen8484;default_type"text/html";set$cc"2";location/{content_by_lua‘ngx.var.cc="5";ifngx.var.arg_location=="at"thenngx.exec("@cc")elsengx.exec("/cc")end‘;}location@cc{return200"thisis@cclocation,cc:$cc";}location/cc{return200"thisis/cclocation,cc:$cc";}}


测试结果:


root@j9~#curl"http://127.0.0.1:8484/?location=at"thisis@cclocation,cc:5root@j9~#curl"http://127.0.0.1:8484/"thisis/cclocation,cc:2root@j9~#



在ngx.exec跳转之前已经把变量cc的值改成5了,但可以看出这两种跳转方式变量cc的值不一样,这是因为ngx.exec跳转到@cc这个location时,从location rewrite阶段开始执行,而跳转到/cc这个location时是从server rewrite阶段开始执行,而set指令是在server块,就是在这个阶段得到执行的,所以$cc又被赋值成2了。


http://www.bkjia.com/HTML5/1222878.htmlwww.bkjia.comtruehttp://www.bkjia.com/HTML5/1222878.htmlTechArticle【技术干货】听阿里云CDN安防技术专家金九讲tengine+lua开发 一、介绍 二、安装 三、运行 四、开发 1.介绍 Tengine:轻量级、高性能、高并发...