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

摘要: 系统介绍tengine的安装、运行和开发,全文包含大量代码示例,由阿里云CDN安防技术专家金九撰写。

1. 介绍

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

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

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

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

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

https://github.com/openresty/lua-nginx-module

2. 安装

2.1、LuaJIT

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

2.2、Tengine

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

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

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

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

3. 运行

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

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

运行tengine:

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

curl访问一下hello_lua:

root@j9 ~/tengine# curl http://localhost/hello_luaLua: hello world!

运行ok。

4、开发

语法

入门

深入

4.1、语法

参考:

Lua简明教程

Lua在线lua学习教程

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状态码;

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

    例如:ngx.status = 200

  • ngx.exit

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

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

    例如:ngx.exit(200)

  • ngx.header

    输出响应头;

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

    例如: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", 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")

例子:

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

测试结果:

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

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

例子

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

  • 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数据

例子

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

测试结果:

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

  • ngx.escape_uri/ngx.unescape_uri

    uri编码解码

  • ngx.encode_args/ngx.decode_args

    参数编码解码

  • ngx.encode_base64/ngx.decode_base64

    BASE64编码解码

  • ngx.md5

    md5加密

例子

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

  • ngx.shared.DICT

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

例子

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

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

4.2.2、指令

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

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

init_by_lua

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

例子:

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

init_worker_by_lua

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

例子:

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

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

例子:

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

rewrite_by_lua

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

需要注意的是:

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

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

例子:

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

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

例子:

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

测试结果:

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

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

content_by_lua

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

例子:略

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长度做限制:

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

测试结果

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

可以看出当参数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模块路径查找顺序,可以从出错信息中看出来:

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

其中,第一个/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;;';

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

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

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

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

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

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

可以看出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, ...)

例子:

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

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

10、子请求

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

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

例子:

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

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

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

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

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

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

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

实验:

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

11、location @xx

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

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

相关推荐