openresty开发系列24--openresty中lua的引入及使用
openresty 引入 lua
一)openresty中nginx引入lua方式
1)xxx_by_lua --->字符串编写方式 2) xxx_by_lua_block ---->代码块方式 3) xxx_by_lua_file ---->直接引用一个lua脚本文件
我们案例中使用内容处理阶段,用content_by_lua演示
-----------------编辑nginx.conf-----------------------
第一种:content_by_lua
location /testlua { content_by_lua "ngx.say('hello world')"; }
输出了hello world
content_by_lua 方式,参数为字符串,编写不是太方便。
----------------------------------------
第二种:content_by_lua_block location /testlua { content_by_lua_block { ngx.say("hello world"); } }
content_by_lua_block {} 表示内部为lua块,里面可以应用lua语句
----------------------------------------
第三种:content_by_lua_file
location /testlua { content_by_lua_file /usr/local/lua/test.lua; }
content_by_lua_file 就是引用外部lua文件
# vi test.lua ngx.say("hello world");
二)openresty使用lua打印输出案例
location /testsay { content_by_lua_block { --写响应头 ngx.header.a = "1" ngx.header.b = "2" --输出响应 ngx.say("a", "b", "<br/>") ngx.print("c", "d", "<br/>") --200状态码退出 return ngx.exit(200) } }
ngx.header:输出响应头; ngx.print:输出响应内容体; ngx.say:通ngx.print,但是会最后输出一个换行符; ngx.exit:指定状态码退出。
三)介绍一下openresty使用lua常用的api
1)ngx.var : 获取Nginx变量 和 内置变量
nginx内置的变量
$arg_name 请求中的name参数 $args 请求中的参数 $binary_remote_addr 远程地址的二进制表示 $body_bytes_sent 已发送的消息体字节数 $content_length HTTP请求信息里的"Content-Length" $content_type 请求信息里的"Content-Type" $document_root 针对当前请求的根路径设置值 $document_uri 与$uri相同; 比如 /test2/test.php $host 请求信息中的"Host",如果请求中没有Host行,则等于设置的服务器名 $hostname 机器名使用 gethostname系统调用的值 $http_cookie cookie 信息 $http_referer 引用地址 $http_user_agent 客户端代理信息 $http_via 最后一个访问服务器的Ip地址。 $http_x_forwarded_for 相当于网络访问路径 $is_args 如果请求行带有参数,返回"?",否则返回空字符串 $limit_rate 对连接速率的限制 $nginx_version 当前运行的nginx版本号 $pid worker进程的PID $query_string 与$args相同 $realpath_root 按root指令或alias指令算出的当前请求的绝对路径。其中的符号链接都会解析成真是文件路径 $remote_addr 客户端IP地址 $remote_port 客户端端口号 $remote_user 客户端用户名,认证用 $request 用户请求 $request_body 这个变量(0.7.58+)包含请求的主要信息。在使用proxy_pass或fastcgi_pass指令的location中比较有意义 $request_body_file 客户端请求主体信息的临时文件名 $request_completion 如果请求成功,设为"OK";如果请求未完成或者不是一系列请求中最后一部分则设为空 $request_filename 当前请求的文件路径名,比如/opt/nginx/www/test.php $request_method 请求的方法,比如"GET"、"POST"等 $request_uri 请求的URI,带参数; 比如http://localhost:88/test1/ $scheme 所用的协议,比如http或者是https $server_addr 服务器地址,如果没有用listen指明服务器地址,使用这个变量将发起一次系统调用以取得地址(造成资源浪费) $server_name 请求到达的服务器名 $server_port 请求到达的服务器端口号 $server_protocol 请求的协议版本,"HTTP/1.0"或"HTTP/1.1" $uri 请求的URI,可能和最初的值有不同,比如经过重定向之类的
ngx.var.xxx
location /var { set $c 3;
#处理业务 content_by_lua_block { local a = tonumber(ngx.var.arg_a) or 0 local b = tonumber(ngx.var.arg_b) or 0 local c = tonumber(ngx.var.c) or 0 ngx.say("sum:", a + b + c ) } }
注意:ngx.var.c 此变量必须提前声明; 另外对于nginx location中使用正则捕获的捕获组可以使用ngx.var[捕获组数字]获取;
location ~ ^/var/([0-9]+) { content_by_lua_block { ngx.say("var[1]:", ngx.var[1] ) } }
2)ngx.req请求模块的常用api
ngx.req.get_headers:获取请求头, 获取带中划线的请求头时请使用如headers.user_agent这种方式;如果一个请求头有多个值,则返回的是table;
-----------test.lua-------------------
local headers = ngx.req.get_headers() ngx.say("============headers begin===============", "<br/>") ngx.say("Host : ", headers["Host"], "<br/>") ngx.say("headers['user-agent'] : ", headers["user-agent"], "<br/>") ngx.say("headers.user_agent : ", headers.user_agent, "<br/>") ngx.say("-------------遍历headers-----------", "<br/>") for k,v in pairs(headers) do if type(v) == "table" then ngx.say(k, " : ", table.concat(v, ","), "<br/>") else ngx.say(k, " : ", v, "<br/>") end end ngx.say("===========headers end============", "<br/>") ngx.say("<br/>")
3)获取请求参数 ngx.req.get_uri_args:获取url请求参数,其用法和get_headers类似; ngx.req.get_post_args:获取post请求内容体,其用法和get_headers类似, 但是必须提前调用ngx.req.read_body()来读取body体 (也可以选择在nginx配置文件使用lua_need_request_body on;开启读取body体, 但是官方不推荐);
ngx.req.get_body_data:为解析的请求body体内容字符串。
---------------test.lua---------------
--get请求uri参数 ngx.say("===========uri get args begin==================", "<br/>") local uri_args = ngx.req.get_uri_args() for k, v in pairs(uri_args) do if type(v) == "table" then ngx.say(k, " : ", table.concat(v, ", "), "<br/>") else ngx.say(k, ": ", v, "<br/>") end end ngx.say("===========uri get args end==================", "<br/>") --post请求参数 ngx.req.read_body() ngx.say("=================post args begin====================", "<br/>") local post_args = ngx.req.get_post_args() for k, v in pairs(post_args) do if type(v) == "table" then ngx.say(k, " : ", table.concat(v, ", "), "<br/>") else ngx.say(k, ": ", v, "<br/>") end end ngx.say("================post args end=====================", "<br/>")
4) ngx.req其他常用的api --请求的http协议版本 ngx.say("ngx.req.http_version : ", ngx.req.http_version(), "<br/>") --请求方法 ngx.say("ngx.req.get_method : ", ngx.req.get_method(), "<br/>") --原始的请求头内容 ngx.say("ngx.req.raw_header : ", ngx.req.raw_header(), "<br/>") --请求的body内容体 ngx.say("ngx.req.get_body_data() : ", ngx.req.get_body_data(), "<br/>") ngx.say("<br/>")
ngx.req.raw_header()这个函数返回值为字符串
5)编码解码
ngx.escape_uri/ngx.unescape_uri : uri编码解码;
ngx.encode_args/ngx.decode_args:参数编码解码;
ngx.encode_base64/ngx.decode_base64:BASE64编码解码;
-------test.lua
--未经解码的请求uri local request_uri = ngx.var.request_uri; ngx.say("request_uri : ", request_uri, "<br/>");
--编码 local escape_uri = ngx.escape_uri(request_uri) ngx.say("escape_uri : ", escape_uri, "<br/>");
--解码 ngx.say("decode request_uri : ", ngx.unescape_uri(escape_uri), "<br/>");
--参数编码 local request_uri = ngx.var.request_uri; local question_pos, _ = string.find(request_uri, '?') if question_pos>0 then local uri = string.sub(request_uri, 1, question_pos-1) ngx.say("uri sub=",string.sub(request_uri, question_pos+1),"<br/>"); --对字符串进行解码 local args = ngx.decode_args(string.sub(request_uri, question_pos+1)) for k,v in pairs(args) do ngx.say("k=",k,",v=", v, "<br/>"); end if args and args.userId then args.userId = args.userId + 10000 ngx.say("args+10000 : ", uri .. '?' .. ngx.encode_args(args), "<br/>"); end end
6)md5加密api --MD5 ngx.say("ngx.md5 : ", ngx.md5("123"), "<br/>")
7)nginx获取时间
之前介绍的os.time()会涉及系统调用,性能比较差,推荐使用nginx中的时间api
ngx.time() --返回秒级精度的时间戳 ngx.now() --返回毫秒级精度的时间戳
就是通过这两种方式获取到的只是nginx缓存起来的时间戳,不是实时的。 所以有时候会出现一些比较奇怪的现象,比如下面代码:
local t1 = ngx.now() for i=1,1000000 do end local t2 = ngx.now() ngx.say(t1, ",", t2) -- t1和t2的值是一样的,why? ngx.exit(200)
正常来说,t2应该大于t1才对,但由于nginx没有及时更新(缓存的)时间戳,所以导致t2和t1获取到的时间戳是一样的。 那么怎样才能强迫nginx更新缓存呢?调用多一个ngx.update_time()函数即可:
local t1 = ngx.now() for i=1,1000000 do end ngx.update_time() local t2 = ngx.now() ngx.say(t1, ",", t2) ngx.exit(200)
8)ngx.re模块中正则表达式相关的api
ngx.re.match ngx.re.sub ngx.re.gsub ngx.re.find ngx.re.gmatch
我们这里只简单的介绍 ngx.re.match,详细用法可以自行去网上学习
ngx.re.match 只有第一次匹配的结果被返回,如果没有匹配,则返回nil;或者匹配过程中出现错误时, 也会返回nil,此时错误信息会被保存在err中。
当匹配的字符串找到时,一个Lua table captures会被返回, captures[0]中保存的就是匹配到的字串, captures[1]保存的是用括号括起来的第一个子模式(捕获分组)的结果, captures[2]保存的是第二个子模式(捕获分组)的结果,依次类似。
---------------------
local m, err = ngx.re.match("hello, 1234", "[0-9]+") if m then ngx.say(m[0]) else if err then ngx.log(ngx.ERR, "error: ", err) return end
ngx.say("match not found") end
上面例子中,匹配的字符串是1234,因此m[0] == "1234", --------------
local m, err = ngx.re.match("hello, 1234", "([0-9])[0-9]+") ngx.say(m[0],"<br/>") ngx.say(m[1])
---------------------------------------------------------
备注:有没有注意到,我们每次修改都要重启nginx,这样太过于麻烦,我们可以用 content_by_lua_file 引入外部lua,这样的话 只要修改外部的lua,就可以了,不需要重启nginx了。 注意需要把lua_code_cache 设置为off,实际生产环境是需要设置为on的
语法:lua_code_cache on | off 默认: on 适用上下文:http、server、location、location if 这个指令是指定是否开启lua的代码编译缓存,开发时可以设置为off,以便lua文件实时生效, 如果是生产线上,为了性能,建议开启。 最终nginx.conf修改为
以后我们只要修改test.lua 文件就可以了。
**********生产环境不建议修改
9)标准日志输出
ngx.log(log_level, ...)
日志输出级别
ngx.STDERR -- 标准输出 ngx.EMERG -- 紧急报错 ngx.ALERT -- 报警 ngx.CRIT -- 严重,系统故障,触发运维告警系统 ngx.ERR -- 错误,业务不可恢复性错误 ngx.WARN -- 告警,业务中可忽略错误 ngx.NOTICE -- 提醒,业务比较重要信息 ngx.INFO -- 信息,业务琐碎日志信息,包含不同情况判断等 ngx.DEBUG -- 调试
-------------------------------------
#user nobody; worker_processes 1;
error_log logs/error.log error; # 日志级别 #pid logs/nginx.pid;
events { worker_connections 1024; }
http { server { listen 80; location / { content_by_lua_block { local num = 55 local str = "string" local obj ngx.log(ngx.ERR, "num:", num) ngx.log(ngx.INFO, " string:", str) print([[i am print]]) ngx.log(ngx.ERR, " object:", obj) } } } }
日志输出级别使用的 error,只有等于或大于这个级别的日志才会输出
ngx.DEBUG ngx.WARN
对于应用开发,一般使用 ngx.INFO 到 ngx.CRIT 就够了。生产中错误日志开启到 error 级别就够了
10)重定向 ngx.redirect
-----重定向
location = /bar { content_by_lua_block { ngx.say([[I am bar]]) } }
location = /foo { rewrite_by_lua_block { return ngx.redirect('/bar'); } }
11)不同阶段共享变量
ngx.ctx 全局共享变量
在 OpenResty 的体系中,可以通过共享内存的方式完成不同工作进程的数据共享, 本地内存方式 去让不同的工作进程共享数据
openresty有不同处理阶段,后面的课程会介绍。在不同的处理阶段,如何共享数据
可以通过 Lua 模块方式完成单个进程内不同请求的数据共享。如何完成单个请求内不同阶段的数据共享呢?
ngx.ctx 表就是为了解决这类问题而设计的。参考下面例子:
location /test { rewrite_by_lua_block { ngx.ctx.foo = 76 } access_by_lua_block { ngx.ctx.foo = ngx.ctx.foo + 3 } content_by_lua_block { ngx.say(ngx.ctx.foo) } }
ngx.ctx.xxxxx
首先 ngx.ctx 是一个表,所以我们可以对他添加、修改。它用来存储基于请求的 Lua 环境数据, 其生存周期与当前请求相同 (类似 Nginx 变量)。它有一个最重要的特性: 单个请求内的 rewrite (重写),access (访问),和 content (内容) 等各处理阶段是保持一致的。
额外注意,每个请求,包括子请求,都有一份自己的 ngx.ctx 表。例如:
location /sub { content_by_lua_block { ngx.say("sub pre: ", ngx.ctx.blah) ngx.ctx.blah = 32 ngx.say("sub post: ", ngx.ctx.blah) } }
location /main { content_by_lua_block { ngx.ctx.blah = 73 ngx.say("main pre: ", ngx.ctx.blah) local res = ngx.location.capture("/sub") ngx.print(res.body) ngx.say("main post: ", ngx.ctx.blah) } }
ngx.ctx 表查询需要相对昂贵的元方法调用,这比通过用户自己的函数参数直接传递基于请求的数据要慢得多。 所以不要为了节约用户函数参数而滥用此 API,因为它可能对性能有明显影响。
由于 ngx.ctx 保存的是指定请求资源,所以这个变量是不能直接共享给其他请求使用的。
更多api使用 https://www.nginx.com/resources/wiki/modules/lua/#nginx-api-for-lua
操作指令 说明 ngx.arg 指令参数,如跟在content_by_lua_file后面的参数 ngx.var 变量,ngx.var.VARIABLE引用某个变量 ngx.ctx 请求的lua上下文 ngx.header 响应头,ngx.header.HEADER引用某个头 ngx.status 响应码
API 说明 ngx.log 输出到error.log print 等价于 ngx.log(ngx.NOTICE, ...) ngx.send_headers 发送响应头 ngx.headers_sent 响应头是否已发送 ngx.resp.get_headers 获取响应头 ngx.timer.at 注册定时器事件 ngx.is_subrequest 当前请求是否是子请求 ngx.location.capture 发布一个子请求 ngx.location.capture_multi 发布多个子请求 ngx.exec ngx.redirect ngx.print 输出响应 ngx.say 输出响应,自动添加'n' ngx.flush 刷新响应 ngx.exit 结束请求 ngx.eof ngx.sleep 无阻塞的休眠(使用定时器实现) ngx.get_phase ngx.on_abort 注册client断开请求时的回调函数 ndk.set_var.DIRECTIVE ngx.req.start_time 请求的开始时间 ngx.req.http_version 请求的HTTP版本号 ngx.req.raw_header 请求头(包括请求行) ngx.req.get_method 请求方法 ngx.req.set_method 请求方法重载 ngx.req.set_uri 请求URL重写 ngx.req.set_uri_args ngx.req.get_uri_args 获取请求参数 ngx.req.get_post_args 获取请求表单 ngx.req.get_headers 获取请求头 ngx.req.set_header ngx.req.clear_header ngx.req.read_body 读取请求体 ngx.req.discard_body 扔掉请求体 ngx.req.get_body_data ngx.req.get_body_file ngx.req.set_body_data ngx.req.set_body_file ngx.req.init_body ngx.req.append_body ngx.req.finish_body ngx.req.socket ngx.escape_uri 字符串的url编码 ngx.unescape_uri 字符串url解码 ngx.encode_args 将table编码为一个参数字符串 ngx.decode_args 将参数字符串编码为一个table ngx.encode_base64 字符串的base64编码 ngx.decode_base64 字符串的base64解码 ngx.crc32_short 字符串的crs32_short哈希 ngx.crc32_long 字符串的crs32_long哈希 ngx.hmac_sha1 字符串的hmac_sha1哈希 ngx.md5 返回16进制MD5 ngx.md5_bin 返回2进制MD5 ngx.sha1_bin 返回2进制sha1哈希值 ngx.quote_sql_str SQL语句转义 ngx.today 返回当前日期 ngx.time 返回UNIX时间戳 ngx.now 返回当前时间 ngx.update_time 刷新时间后再返回 ngx.localtime ngx.utctime ngx.cookie_time 返回的时间可用于cookie值 ngx.http_time 返回的时间可用于HTTP头 ngx.parse_http_time 解析HTTP头的时间 ngx.re.match ngx.re.find ngx.re.gmatch ngx.re.sub ngx.re.gsub ngx.shared.DICT ngx.shared.DICT.get ngx.shared.DICT.get_stale ngx.shared.DICT.set ngx.shared.DICT.safe_set ngx.shared.DICT.add ngx.shared.DICT.safe_add ngx.shared.DICT.replace ngx.shared.DICT.delete ngx.shared.DICT.incr ngx.shared.DICT.flush_all ngx.shared.DICT.flush_expired ngx.shared.DICT.get_keys ngx.socket.udp udpsock:setpeername udpsock:send udpsock:receive udpsock:close udpsock:settimeout ngx.socket.tcp tcpsock:connect tcpsock:sslhandshake tcpsock:send tcpsock:receive tcpsock:receiveuntil tcpsock:close tcpsock:settimeout tcpsock:setoption tcpsock:setkeepalive tcpsock:getreusedtimes ngx.socket.connect ngx.thread.spawn ngx.thread.wait ngx.thread.kill coroutine.create coroutine.resume coroutine.yield coroutine.wrap coroutine.running coroutine.status ngx.config.debug 编译时是否有 --with-debug选项 ngx.config.prefix 编译时的 --prefix选项 ngx.config.nginx_version 返回nginx版本号 ngx.config.nginx_configure 返回编译时 ./configure的命令行选项 ngx.config.ngx_lua_version 返回ngx_lua模块版本号 ngx.worker.exiting 当前worker进程是否正在关闭(如reload、shutdown期间) ngx.worker.pid 返回当前worker进程的pid 常量说明 ngx.OK (0) ngx.ERROR (-1) ngx.AGAIN (-2) ngx.DONE (-4) ngx.DECLINED (-5) ngx.nil
HTTP 请求方式 ngx.HTTP_GET ngx.HTTP_HEAD ngx.HTTP_PUT ngx.HTTP_POST ngx.HTTP_DELETE ngx.HTTP_OPTIONS ngx.HTTP_MKCOL ngx.HTTP_COPY ngx.HTTP_MOVE ngx.HTTP_PROPFIND ngx.HTTP_PROPPATCH ngx.HTTP_LOCK ngx.HTTP_UNLOCK ngx.HTTP_PATCH ngx.HTTP_TRACE
HTTP 返回状态 ngx.HTTP_OK (200) ngx.HTTP_CREATED (201) ngx.HTTP_SPECIAL_RESPONSE (300) ngx.HTTP_MOVED_PERMANENTLY (301) ngx.HTTP_MOVED_TEMPORARILY (302) ngx.HTTP_SEE_OTHER (303) ngx.HTTP_NOT_MODIFIED (304) ngx.HTTP_BAD_REQUEST (400) ngx.HTTP_UNAUTHORIZED (401) ngx.HTTP_FORBIDDEN (403) ngx.HTTP_NOT_FOUND (404) ngx.HTTP_NOT_ALLOWED (405) ngx.HTTP_GONE (410) ngx.HTTP_INTERNAL_SERVER_ERROR (500) ngx.HTTP_METHOD_NOT_IMPLEMENTED (501) ngx.HTTP_SERVICE_UNAVAILABLE (503) ngx.HTTP_GATEWAY_TIMEOUT (504)
|
请发表评论