• 【技术干货】听阿里云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 -l
    32
    root@j9 ~/lua-5.1.5/src# ls *.h | wc -l 
    23
    root@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 -c http://luajit.org/download/LuaJIT-2.0.4.tar.gz
    tar xzvf LuaJIT-2.0.4.tar.gz
    cd LuaJIT-2.0.4
    make install PREFIX=/usr/local/luajit
    #注意环境变量!
    export LUAJIT_LIB=/usr/local/luajit/lib
    export LUAJIT_INC=/usr/local/luajit/include/luajit-2.0
    

    2.2、Tengine

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

    git clone https://github.com/alibaba/tengine.git
    cd tengine
    ./configure --prefix=/opt/tengine --with-http_lua_module
    make
    make install
    

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

    wget http://nginx.org/download/nginx-1.7.8.tar.gz
    tar xvf nginx-1.7.8.tar.gz
    cd nginx-1.7.8
    mkdir modules
    cd modules
    git clone https://github.com/openresty/lua-nginx-module.git
    cd ..
    ./configure --prefix=/opt/nginx --add-module=./modules/lua-nginx-module/
    make
    make install
    

    3. 运行

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

    worker_processes  1;
    error_log  logs/error.log;
    pid        logs/nginx.pid;
    events {
        worker_connections  1024;
    }
    http {
        include       mime.types;
        default_type  application/octet-stream;
        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for"';
        access_log  logs/access.log  main;
        sendfile        on;
        keepalive_timeout  65;
        server {
            listen       80;
            server_name  localhost;
            location / {
                root   html;
                index  index.html index.htm;
            }
            location /hello_lua {
                content_by_lua '
                    ngx.say("Lua: hello world!")
                ';
            }
        }
    }
    

    运行tengine:

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

    curl访问一下hello_lua:

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

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

    注意,设置状态码仅在响应头发送前有效。当调用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](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 {               
            listen       9898;        
            location / {                 
                default_type "text/html";       
                content_by_lua '             
                    local headers_sent_1 = ngx.headers_sent
                    ngx.header["X-Cache"] = "HIT"     
                    ngx.header.Y_Cache = "MISS"      
                    ngx.header.Z_Cache = {"AA", "BB"}  
                    ngx.status = 602      
                    local headers_sent_2 = ngx.headers_sent
                    ngx.print("a", "b")  
                    local headers_sent_3 = ngx.headers_sent
                    ngx.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.log test ok")
                    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/" -i
    HTTP/1.1 602 
    Server: Tengine/2.2.0
    Date: Mon, 19 Oct 2015 16:10:42 GMT
    Content-Type: text/html
    Transfer-Encoding: chunked
    Connection: keep-alive
    X-Cache: HIT
    Y-Cache: MISS
    Z-Cache: AA
    Z-Cache: BB
    abcd
    ef
    headers_sent_1: false
    headers_sent_2: false
    headers_sent_3: true
    root@j9 ~# curl "http://127.0.0.1:9898/redirect" -i
    HTTP/1.1 301 Moved Permanently
    Server: Tengine/2.2.0
    Date: Mon, 19 Oct 2015 16:18:16 GMT
    Content-Type: text/html
    Content-Length: 284
    Connection: keep-alive
    Location: http://www.taobao.org
    Powered by Tengine/2.2.0root@j9 ~#

    - ngx.var

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

    例子

    server {               
            listen       9898;        
            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: bbbb
    root@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())
            local headers = ngx.req.get_headers()
            ngx.say("===========headers============")
            ngx.say("Host: ", headers["Host"])
            ngx.say("user-agent: ", headers.user_agent)
            ngx.say("===========all headers========")
            for k,v in pairs(headers) do
              if type(v) == "table" then
                ngx.say(k, ": ", table.concat(v, ","))
              else
                ngx.say(k, ": ", v)
              end
            end
            ngx.say("===========args===============")
            local args = ngx.req.get_uri_args()
            for k,v in pairs(args) do
              ngx.say(k, ": ", v)
            end                                                         
            ngx.say("===========body===============")
            ngx.say("body data: ", ngx.req.get_body_data())
            ngx.req.read_body()
            local post_args = ngx.req.get_post_args()
            for k,v in pairs(post_args) do
              ngx.say(k, ": ", v)
            end
            ngx.say("body data: ", 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=33 HTTP/1.1
    User-Agent: curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3
    Host: 127.0.0.1:9898
    Accept: */*
    Content-Length: 14
    Content-Type: application/x-www-form-urlencoded
    ===========headers============
    Host: 127.0.0.1:9898
    user-agent: curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3
    ===========all headers========
    host: 127.0.0.1:9898
    content-type: application/x-www-form-urlencoded
    accept: */*
    content-length: 14
    user-agent: curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3
    ===========args===============
    b: 22
    a: 11
    c: 33
    ===========body===============
    body data: nil
    d: 11
    f: 33
    e: 22
    body data: d=11&e=22&f=33
    root@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 '
            local request_uri = ngx.var.request_uri
            local args = {a=11, b=22}
            ngx.say("request uri: ", request_uri)
            ngx.say("unescape request uri: ", ngx.unescape_uri(request_uri))
            ngx.say("encode args: ", ngx.encode_args(args))
            ngx.say("encode base64 request uri: ", 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"    
     request uri: /code?name=%E9%87%91%E4%B9%9D    
    unescape request uri: /code?name=金九    
    encode args: a=11&b=22    
    encode base64 request uri: L2NvZGU/bmFtZT0lRTklODclOTElRTQlQjklOUQ=    
    md5(123456): e10adc3949ba59abbe56e057f20f883e    
     root@j9 ~#
    

    - ngx.shared.DICT

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

    例子

    lua_shared_dict cc_shared_data 16k;
        server {
            listen       9999;
            default_type "text/html";
            location ^~ /shared_data {
                content_by_lua '
                    local shared_data = ngx.shared.cc_shared_data
                    local i = shared_data:get("i")
                    if not i then
                      shared_data:set("i", 1)
                    end
                    i = shared_data:incr("i", 1)
                    ngx.say("i: ", i)
                ';
            }
        }
    

    测试结果

    root@j9 ~# curl "http://127.0.0.1:9999/shared_data"
    i: 2
    root@j9 ~# curl "http://127.0.0.1:9999/shared_data"
    i: 3
    root@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_file  loading-config  http  nginx master进程加载配置时执行;通常用于初始化全局配置/预加载Lua模块
    init_worker_by_lua/init_worker_by_lua_file  starting-worker  http 每个nginx worker进程启动时调用的计时器,如果master进程不允许则只会在init_by_lua之后调用;通常用于定时拉取配置/数据,或者后端服务的健康检查
    set_by_lua/set_by_lua_file rewrite server,server if,location,location if  设置nginx变量,可以实现复杂的赋值逻辑;此处是阻塞的,Lua代码要做到非常快
    rewrite_by_lua/rewrite_by_lua_file rewrite tail http,server,location,location ifrewrite 阶段处理,可以实现复杂的转发/重定向逻辑
    access_by_lua/access_by_lua_file access tail  http,server,location,location if  请求访问阶段处理,用于访问控制
    content_by_lua/content_by_lua_file  content location,location if  内容处理器,接收请求处理并输出响应
    header_filter_by_lua/header_filter_by_lua_file output-header-filter http,server,location,location if  设置header和cookie
    body_filter_by_lua/body_filter_by_lua_file output-body-filter http,server,location,location if  对响应数据进行过滤,比如截断、替换
    log_by_lua/log_by_lua_file log  http,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_lua ok")
        ';
        server {
            listen       9292;
            default_type "text/html";
            location / {
                content_by_lua '
                    local arg_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: 111
    root@j9 ~#
    

    **init_worker_by_lua**

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

    例子:

    worker_processes  2;  
    http {
        #这里省略了其他配置
        init_worker_by_lua '
            ngx.log(ngx.ERR, "test init_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 {
        listen       9393;
        default_type "text/html";
        location /add {
            set $diff '';
            set $double_c '';
            set_by_lua $sum '
                local a = ngx.var.arg_a
                local b = ngx.var.arg_b
                ngx.var.diff = a - b
                ngx.var.double_c = 2 * tonumber(ngx.arg[1])
                return a + b;
            ' $arg_c;
            return 200 "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处理阶段的最后。

    需要注意的是:

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

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

    例子:

    server {
        listen       9494;
        default_type "text/html";
        location /rewrite_by_lua {
            set $a 11;
            rewrite_by_lua '
                ngx.var.a = "aa"
                if ngx.var.arg_exit == "ok" then
                  ngx.exit(ngx.OK)
                else
                  ngx.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: aa
    root@j9 ~# curl "http://127.0.0.1:9494/rewrite_by_lua?exit=200"
    root@j9 ~# 
    access_by_lua
    

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

    例子:

    server {
        listen 9595;
        default_type "text/html";
        location / {
            access_by_lua '
                local auth = ngx.var.arg_auth;
                local key = "alicdnj9";
                if ngx.md5(key) ~= auth then
                    return ngx.exit(403)
                end
            ';
            content_by_lua '
                ngx.say("access ok")
            ';
        }
    }
    

    测试结果:

    root@j9 ~# curl "http://127.0.0.1:9595/?auth=xx"             
    Powered by Tengine/2.2.0root@j9 ~# echo -n alicdnj9 | md5sum            
    50652c84270d22210593318f5d3016a1  -
    root@j9 ~# curl "http://127.0.0.1:9595/?auth=50652c84270d22210593318f5d3016a1"
    access ok
    root@j9 ~#
    

    注意,如果在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长度做限制:

    server {
        listen 9696;
        default_type "text/html";
        set $content_len 0;
        location / {
            header_filter_by_lua '
                -- 先去掉Content-Length头部,转成Chunked传输
                ngx.header.content_length = nil
            ';                                                                     
            body_filter_by_lua '
                local content_length = #ngx.arg[1]
                content_length = ngx.var.content_len + content_length
                ngx.var.content_len = content_length
                -- 最多只能传输10字节的body,否则直接关掉连接
                if content_length > 10 then
                    return ngx.ERROR
                end
            ';                                                                     
            content_by_lua '
                for i=1, ngx.var.arg_len do
                    ngx.print("a")
                end
            ';
        }  
    }
    

    测试结果

    root@j9 ~# curl "http://127.0.0.1:9696/?len=10" -i
    HTTP/1.1 200 OK
    Server: Tengine/2.2.0
    Date: Mon, 26 Oct 2015 01:48:23 GMT
    Content-Type: text/html
    Transfer-Encoding: chunked
    Connection: keep-alive
    aaaaaaaaaa
    root@j9 ~# curl "http://127.0.0.1:9696/?len=11" -i 
    curl: (52) Empty reply from server
    root@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 {
        listen 9797;
        default_type "text/html";
        location / {
            content_by_lua '
                local a = 1
                local b = {b = 1}
                local status = require("status")
                ngx.say("a: ", a, ", b: ", b.b, " counter: ", status.counter)
                a = a + 1
                b.b = b.b + 1
                status.counter = (status.counter or 0) + 1
            ';
        }
    }
    

    其中status.lua为:

    local m = {}
    m.counter = 1
    return m
    

    测试结果:

    root@j9 ~# curl "http://127.0.0.1:9797/"
    a: 1, b: 1 counter: 1
    root@j9 ~# curl "http://127.0.0.1:9797/"
    a: 1, b: 1 counter: 2
    root@j9 ~# curl "http://127.0.0.1:9797/"
    a: 1, b: 1 counter: 3
    root@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, ...)

    例子:

    local delay = 5
    local handler
    handler = function (premature)
        -- do some routine job in Lua just like a cron job
        if premature then
            return
        end
        local ok, err = ngx.timer.at(delay, handler)
        if not ok then
            ngx.log(ngx.ERR, "failed to create the timer: ", err)
            return
        end
    end
    local ok, err = ngx.timer.at(delay, handler)
    if not ok then
        ngx.log(ngx.ERR, "failed to create the timer: ", err)
        return
    end
    

    注意:在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时代理客户端跳转

    server {
        listen 8181;                                                            
        default_type "text/html";
        location /test {
            content_by_lua '
                local res = ngx.location.capture("/get" .. ngx.var.request_uri, { method = ngx.HTTP_HEAD })
                if res.status == 200 then
                    ngx.exec("/get" .. ngx.var.request_uri)
                elseif res.status == 301 or res.status == 302 then
                    location = res.header["Location"]
                    local m, err = ngx.re.match(location, "http://([^/]+)(/.*)")
                    if not m then
                        ngx.exit(500)
                    end
                    host = m[1]
                    uri = m[2]
                    ngx.exec("/redirect/" .. host .. "/" .. ngx.var.request_uri)
                else
                    ngx.exit(res.status)
                end
            ';
        }
        location ~ /redirect/([^/]*)/([^/]*) {
            proxy_pass http://$1/$2?$args;
        }
        location /get {
            if ($arg_tag = "1") {
                return 302 "http://127.0.0.1:8282/$request_uri";
            }
            return 200 "ok";
        }
    }
    server {
        listen 8282;
        default_type "text/html";
        location / {
            return 200 "redirect ok, args: $args";
        }
    }
    

    测试结果:

    root@j9 ~# curl "http://127.0.0.1:8181/test?tag=0" -i
    HTTP/1.1 200 OK
    Server: Tengine/2.2.0
    Date: Mon, 26 Oct 2015 15:17:10 GMT
    Content-Type: text/html
    Content-Length: 2
    Connection: keep-alive
    ok
    root@j9 ~# curl "http://127.0.0.1:8181/test?tag=1" -i 
    HTTP/1.1 200 OK
    Server: Tengine/2.2.0
    Date: Mon, 26 Oct 2015 15:17:14 GMT
    Content-Type: text/html
    Content-Length: 19
    Connection: keep-alive
    redirect ok, args: tag=1
    root@j9 ~#
    

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

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

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

    实验:

    server {
        listen 8383;
        default_type "text/html";
        set $cc "cc";
        location /test {
            content_by_lua '
                ngx.var.cc = "11"
                local res = ngx.location.capture("/get" .. ngx.var.request_uri) 
                if res.status == 200 then
                    ngx.say(res.body)
                    ngx.say("test cc: ", ngx.var.cc)
                else
                    ngx.exit(res.status)
                end
            ';
        }
        location /get {
            content_by_lua '
                ngx.say("get cc: ", ngx.var.cc)
                ngx.var.cc = "22"
            ';
        }
    }
    

    结果:

    root@j9 ~# curl "http://127.0.0.1:8383/test"
    get cc: cc
    test cc: 11
    root@j9 ~#
    

    11、location @xx

    server {
        listen 8484;
        default_type "text/html";
        set $cc "2";
        location / {
            content_by_lua '
                ngx.var.cc = "5";
                if ngx.var.arg_location == "at" then
                    ngx.exec("@cc")
                else
                    ngx.exec("/cc")
                end
            ';
        }
        location @cc {
            return 200 "this is @cc location, cc: $cc";
        }                                                                          
        location /cc {
            return 200 "this is /cc location, cc: $cc";
        }
    }
    

    测试结果:

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

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

  • 相关阅读:
    游戏文字自动断行需要,还得从 UTF-8 讲起
    史上最明白的 NULL、0、nullptr 区别分析(老师讲N篇都没讲明白的东东),今天终于明白了,如果和我一样以前不明白的可以好好的看看...
    django -- ORM实现作者增删改查
    selenium--操作JS弹框
    selenium--多窗口操作
    django -- ORM实现图书增删改查
    django -- ORM实现出版社增删改查
    selenium--等待的三种方式
    postman使用--Monitor
    django -- 实现ORM登录
  • 原文地址:https://www.cnblogs.com/xuelisherry/p/7347168.html
Copyright © 2020-2023  润新知