nginx+lua tips (一)

公西兴业
2023-12-01

关于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 the add_before_body add_after_body auth_request echo_location echo_location_async echo_subrequest , or echo_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值)表示。 


 类似资料: