nginx+lua tips (一),nginxluatips


关于nginx+lua的Tips

主要参考:https://github.com/openresty/lua-nginx-module,不完全翻译

一、关于nginx lua
⭐️require和dofile这种调用ngx.location.capture*,ngx.exec,ngx.exit等类似requiring yielding in the top-level scope的function,会抛attempt to yield across C-call boundary异常,所以把这些放function里
⭐️lua5.1的解释器的VM不是完全可恢复的,用luajit2.0避免。
⭐️变量作用域 用local xxx = require(‘xxx') 要有local声明,否则会变成全局变量,尽量不要用全局变量 全局变量和与他关联的nginx request handler有相同的声明周期。 用lua-releng可以检测全局变量。
⭐️Locations Configured by Subrequest Directives of Other Modules The ngx.location.capture and ngx.location.capture_multi directives cannot capture locations that include theadd_before_bodyadd_after_bodyauth_requestecho_locationecho_location_asyncecho_subrequest, orecho_subrequest_async directives.
⭐️Special Escaping Sequences 反斜杠作为逃逸字符,用lua的正则时反斜杠要转义两次。  # nginx.conf
 ? location /test {
 ?     content_by_lua '
 ?         local regex = "\d+"  -- THIS IS WRONG!!
 ?         local m = ngx.re.match("hello, 1234", regex)
 ?         if m then ngx.say(m[0]) else ngx.say("not matched!") end
 ?     ';
 ? }
 # evaluates to "not matched!"
比如local regex = "\d+"  -- THIS IS WRONG!,不能匹配数字,要用local regex = "\\\\d+" 这里第一次被nginx conf解析成的\\d+,然后再被解析成lua的\d+ 或者用[[….]]  # nginx.conf
 location /test {
     content_by_lua '
         local regex = [[\\d+]]
         local m = ngx.re.match("hello, 1234", regex)
         if m then ngx.say(m[0]) else ngx.say("not matched!") end
     ';
 }  # evaluates to "1234" 这里被nginx解析成\d+,在[[..]]里可以直接被lua用。 如果正则本身有[..],就用[=[…]=]  # nginx.conf
 location /test {
     content_by_lua '
         local regex = [=[[0-9]+]=]
         local m = ngx.re.match("hello, 1234", regex)
         if m then ngx.say(m[0]) else ngx.say("not matched!") end
     ';
 }  # evaluates to "1234" 但是用*_by_lua_file的方式载入,只会被lua解析一次,所以  -- test.lua
 local regex = "\\d+"
 local m = ngx.re.match("hello, 1234", regex)
 if m then ngx.say(m[0]) else ngx.say("not matched!"end
 -- evaluates to "1234"
这样就可以 或者  -- test.lua
 local regex = [[\d+]]
 local m = ngx.re.match("hello, 1234", regex)
 if m then ngx.say(m[0]) else ngx.say("not matched!"end  -- evaluates to "1234"
二、nginx lua module相关指令 ⭐️lua_use_default_type
⭐️lua_code_cache:缓存lua代码,为off不缓存,性能影响大
⭐️lua_regex_cache_max_entries 缓存正则
⭐️lua_regex_match_limit 缓存正则命中
⭐️la_package_path:lua module 路径,两个分号“;;”代表默认搜索路径
⭐️la_package_cpath:lua c module路径
⭐️init_by_lua:(loading-config阶段) 配置地点:http 从v0.9.17开始,不推荐用这个了,推荐用init_by_lua_block 当nginx接收到HUP重新加载config file的时候,lua VM也会重新创建,init_by_lua也会在新lua VM上重新运行,这个时候,lua_code_cache会被关闭,所以在这种特殊的情况,每个request都会创建一个单独的lua VM,从而每个单独的lua VM都会运行init_by_lua。 通常可以在这里注册lua全局变量和预加载lua module  init_by_lua 'cjson = require "cjson"';

 server {
     location = /api {
         content_by_lua '
             ngx.say(cjson.encode({dog = 5, cat = 6}))
         ';
     }
 }
也可以初始化字典:  lua_shared_dict dogs 1m;

 init_by_lua '
     local dogs = ngx.shared.dogs;
     dogs:set("Tom", 56)
 ';

 server {
     location = /api {
         content_by_lua '
             local dogs = ngx.shared.dogs;
             ngx.say(dogs:get("Tom"))
         ';
     }
 }
nginx master process 加载nginx的conf文件时,在lua VM全局级别运行的lua代码,重启重新加载,可以require,lua_shared_dic等,lua_shared_dict的内容不会因配置文件重新加载而清空,因为nginx先启动新的worker processes,共享这块内存。不要再这里初始化自己的lua全局变量,会使命名空间污染。 推荐用合适的lua module files(不要用lua的module() function,因为他会污染全局变量空间)和调用require()加载module files在init_by_lua或者其他的context里。(require()会将加载的lua module缓存在全局的package.loaded table里,所以在整个lua VM里,module只加载注册一次) 官方说这里可以运行一些阻塞I/O的命令,因为阻塞server start还好,甚至nginx本身在configure-loading的时候就是阻塞的(至少在解析upstream的host name的时候是)。
⭐️init_by_lua_block 基本同上,带block的就是把lua用{}括起来了。since v0.9.17  init_by_lua_block {
     print("I need no extra escaping here, for example: \r\nblah")
 }

⭐️init_by_lua_file:大致同上
⭐️init_worker_by_lua:(starting-worker阶段) nginx worker process启动的时候运行的lua代码,常用来nginx.timeer.at这种定时健康检查
⭐️init_worker_by_lua_file:大致同上
⭐️set_by_lua: (rewrite阶段) 语法:set_by_lua $res <lua-script-str> [$arg1 $arg2...] 操作输入参数$arg1 $arg2运行lua代码,返回到$res,此时通过lua脚本可以取得nginx.arg table里面的参数(index从1开始计数) 阻塞,lua脚本要short,fast,因其是植入到ngx_http_rewrite_module里的,这里不支持非阻塞I/O 不能运行下面这些: Output API functions (e.g., ngx.say and ngx.send_headers) Control API functions (e.g., ngx.exit) Subrequest API functions (e.g., ngx.location.capture and ngx.location.capture_multi) Cosocket API functions (e.g., ngx.socket.tcp and ngx.req.socket). Sleeping API function ngx.sleep 只能传出一个变量,解决办法是用ngx.var  location /foo {
     set $diff ''# we have to predefine the $diff variable here

     set_by_lua $sum '
         local a = 32
         local b = 56

         ngx.var.diff = a - b;  -- write to $diff directly
         return a + b;          -- return the $sum value normally
     ';

     echo "sum = $sum, diff = $diff";  } 和nginx的变量会按顺序运行:  set $foo 32;
 set_by_lua $bar 'return tonumber(ngx.var.foo) + 1';
 set $baz "bar: $bar"# $baz == "bar: 33"

需要ngx_devel_kit module
⭐️set_by_lua_file:大致同上 lua code cache打开时(默认打开),user code只有在第一个请求到来时加载并被缓存,nginx配置文件被修改时,必须reload,lua code cache在开发时可以被临时停用避免重复reload nginx。
⭐️content_by_lua 使用环境:location,location if 阶段:content 是一个content handler,每个request都运行指定的lua代码。 不能把这个命令和其他的content handler命令用在同一个location里,比如不能喝proxy_pass同时出现在一个location里。
⭐️content_by_lua_file 基本同上,nginx变量可以用于动态path  # WARNING: contents in nginx var must be carefully filtered,
 # otherwise there'll be great security risk!
 location ~ ^/app/([-_a-zA-Z0-9/]+) {
     set $path $1;
     content_by_lua_file /path/to/lua/app/root/$path.lua;
 }
lua code cache打开时(默认打开),user code只有在第一个请求到来时加载并被缓存,nginx配置文件被修改时,必须reload,lua code cache在开发时可以被临时停用避免重复reload nginx。
⭐️rewrite_by_lua 使用环境:http,server,location,location if 阶段:rewrite tail 就是个rewrite阶段的handler,每个request都运行指定的lua代码。 这个handler总是运行在ngx_http_rewrite_module,所以这样写好使:  location /foo {
     set $a 12# create and initialize $a
     set $b ""# create and initialize $b
     rewrite_by_lua 'ngx.var.b = tonumber(ngx.var.a) + 1';
     echo "res = $b";  } 因为set $a 12 和set $b “”在rewrite_by_lua之前运行,但是这样就不好使了:  ?  location /foo {
 ?      set $a 12# create and initialize $a
 ?      set $b ''# create and initialize $b
 ?      rewrite_by_lua 'ngx.var.b = tonumber(ngx.var.a) + 1';
 ?      if ($b = '13') {
 ?         rewrite /bar redirect;
 ?         break;
 ?      }
 ?
 ?      echo "res = $b";  ?  } 因为if在rewrite_by_lua之前运行了,之后才设置的b=a+1,所以好改成这样:  location /foo {
     set $a 12# create and initialize $a
     set $b ''# create and initialize $b
     rewrite_by_lua '
         ngx.var.b = tonumber(ngx.var.a) + 1
         if tonumber(ngx.var.b) == 13 then
             return ngx.redirect("/bar");
         end
     ';

     echo "res = $b";  } ngx_eval模块可以约等于rewrite_by_lua:
 location / {
     eval $res {
         proxy_pass http://foo.com/check-spam;
     }

     if ($res = 'spam') {
         rewrite ^ /terms-of-use.html redirect;
     }

     fastcgi_pass ...;
 }

can be implemented in ngx_lua as:

 location = /check-spam {
     internal;
     proxy_pass http://foo.com/check-spam;
 }

 location {
     rewrite_by_lua '
         local res = ngx.location.capture("/check-spam")
         if res.body == "spam" then
             return ngx.redirect("/terms-of-use.html")
         end
     ';

     fastcgi_pass ...;
 }
就像其他rewrite阶段的处理一样,rewrite_by_lua也可以在子请求中运行。
注:在rewrite_by_lua中调用ngx.exit(ngx.OK)后,nginx的请求处理会继续执行content handler,要想在rewrite_by_lua中终止当前请求,用大于200(ngx.HTTP_OK)小于300(ngx.HTTP_SPECIAL_RESPONSE)的状态表示正常,用ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)或类似的表示失败。
如果调用了nginx_http_rewrite_module的rewrite指令改变了URI,并且内部跳转,那当前location中的rewrite_by_lua和rewrite_by_lua_file不会执行。  location /foo {
     rewrite /bar;
     rewrite_by_lua 'ngx.exit(503)';
 }
 location /bar {
     ...  } 上面的ngx.exit(503)永远不会运行,如果用rewrite ^ /bar last,效果一样,但是换成break,这里就不会跳转,rewrite_by_lua就会运行了。
rewrite_by_lua在rewrite的request-processing阶段总会运行,除非打开了rewrite_by_lua_no_postpone。
⭐️rewrite_by_lua_file 基本同上,文件也可以用nginx参数,但是有风险,不推荐。 lua code cache打开时(默认打开),user code只有在第一个请求到来时加载并被缓存,nginx配置文件被修改时,必须reload,lua code cache在开发时可以被临时停用避免重复reload nginx。
⭐️access_by_lua 使用环境:http, server, location, location if 阶段:access tail 跟access handler一样,每个request都执行lua的代码。 在ngx_http_access_module之后运行  location {
     deny    192.168.1.1;
     allow   192.168.1.0/24;
     allow   10.1.1.0/16;
     deny    all;

     access_by_lua '
         local res = ngx.location.capture("/mysql", { ... })
         ...
     ';

     # proxy_pass/fastcgi_pass/...  } 这里是判断查mysql的请求的ip是否在黑名单里,是就阻止访问。 ngx_auth_request模块和access_by_lua类似:
 location / {
     auth_request /auth;

     # proxy_pass/fastcgi_pass/postgres_pass/...
 }

can be implemented in ngx_lua as:

 location {
     access_by_lua '
         local res = ngx.location.capture("/auth")

         if res.status == ngx.HTTP_OK then
             return
         end

         if res.status == ngx.HTTP_FORBIDDEN then
             ngx.exit(res.status)
         end

         ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
     ';

     # proxy_pass/fastcgi_pass/postgres_pass/...  } 注:access_by_lua里运行ngx.exit(ngx.OK)会继续运行nginx request processing control后面的content handler,要在rewrite_by_lua中终止当前请求,用大于200(ngx.HTTP_OK)小于300(ngx.HTTP_SPECIAL_RESPONSE)的状态表示正常,用ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)或类似的表示失败。
⭐️access_by_lua_file 基本同上,文件也可以用nginx参数,但是有风险,不推荐。 lua code cache打开时(默认打开),user code只有在第一个请求到来时加载并被缓存,nginx配置文件被修改时,必须reload,lua code cache在开发时可以被临时停用避免重复reload nginx。
⭐️header_filter_by_lua 使用环境:http, server, location, location if 阶段:output-header-filter 就是用lua做个output header filter 不能用的几个指令: Output API functions (e.g., ngx.say and ngx.send_headers) Control API functions (e.g., ngx.redirect and ngx.exec) Subrequest API functions (e.g., ngx.location.capture and ngx.location.capture_multi) Cosocket API functions (e.g., ngx.socket.tcp and ngx.req.socket). 一个覆盖或添加header的例子:  location {
     proxy_pass http://mybackend;
     header_filter_by_lua 'ngx.header.Foo = "blah"';
 }

⭐️header_filter_by_lua_file 跟header_filter_by_lua一样。
⭐️body_filter_by_lua 使用环境:http, server, location, location if 阶段:output-body-filter 就是用lua做个output body filter。 输入的字符用ngx.arg[1]取(lua的string),表明body流结尾的eof标记用ngx.arg[2](lua的boolean值)表示。 


相关内容

    暂无相关文章