文档章节

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

我是王雪梨
 我是王雪梨
发布于 2017/08/11 15:15
字数 4471
阅读 18
收藏 0
点赞 0
评论 0

一、介绍
二、安装
三、运行
四、开发

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

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简明教程 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状态码; <u>注意,设置状态码仅在响应头发送前有效。当调用ngx.say或者ngx.print时自动发送响应状态码(默认为200);可以通ngx.headers_sent来判断是否发送了响应状态码。</u> 例如:ngx.status = 200

  • ngx.exit 设置响应HTTP状态码并退出; <u>注意,设置状态码仅在响应头发送前有效,并且该函数调用之后该函数后面的lua将被忽略掉,因为已经exit了。</u> 例如:ngx.exit(200)

  • ngx.header 输出响应头; <u>注意,头部字段中含有横杠(-)的要转换成下划线(_),ngx_lua模块自动将_转换成-。</u> 例如: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")

例子:

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

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<h1>301 Moved Permanently</h1>
<p>The requested resource has been assigned a new permanent URI.</p>
<hr/>Powered by Tengine/2.2.0</body>
</html>
root@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

4.2.2、指令

指令阶段范围说明
init_by_lua/init_by_lua_fileloading-confighttpnginx master进程加载配置时执行;通常用于初始化全局配置/预加载Lua模块
init_worker_by_lua/init_worker_by_lua_filestarting-workerhttp每个nginx worker进程启动时调用的计时器,如果master进程不允许则只会在init_by_lua之后调用;通常用于定时拉取配置/数据,或者后端服务的健康检查
set_by_lua/set_by_lua_filerewriteserver,server if,location,location if设置nginx变量,可以实现复杂的赋值逻辑;此处是阻塞的,Lua代码要做到非常快
rewrite_by_lua/rewrite_by_lua_filerewrite tailhttp,server,location,location if rewrite阶段处理,可以实现复杂的转发/重定向逻辑
access_by_lua/access_by_lua_fileaccess tailhttp,server,location,location if请求访问阶段处理,用于访问控制
content_by_lua/content_by_lua_filecontentlocation,location if内容处理器,接收请求处理并输出响应
header_filter_by_lua/header_filter_by_lua_fileoutput-header-filterhttp,server,location,location if设置header和cookie
body_filter_by_lua/body_filter_by_lua_fileoutput-body-filterhttp,server,location,location if对响应数据进行过滤,比如截断、替换
log_by_lua/log_by_lua_fileloghttp,server,location,location if log阶段处理,比如记录访问量/统计平均响应时间

更详细的解释请参考官网: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
root@j9 ~#

rewrite_by_lua

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

<u>需要注意的是: 1、在长连接中如果调用了ngx.exit(200)一个请求,则需要调用ngx.req.discard_body(),否则nginx可能会把当前请求的body当成header解析,从而导致400错误返回码并且长连接被关闭。 2、如果该阶段调用了ngx.exit(ngx.OK),content_by_lua阶段仍然能得到执行。</u>

例子:

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"             
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<h1>403 Forbidden</h1>
<p>You don't have permission to access the URL on this server. Sorry for the inconvenience.<br/>
Please report this message and include the following information to us.<br/>
Thank you very much!</p>
<table>
<tr>
<td>URL:</td>
<td>http://127.0.0.1:9595/?auth=xx</td>
</tr>
<tr>
<td>Server:</td>
<td>j9</td>
</tr>
<tr>
<td>Date:</td>
<td>2015/10/27 16:47:20</td>
</tr>
</table>
<hr/>Powered by Tengine/2.2.0</body>
</html>
root@j9 ~# echo -n alicdnj9 | md5sum            
50652c84270d22210593318f5d3016a1  -
root@j9 ~# curl "http://127.0.0.1:9595/?auth=50652c84270d22210593318f5d3016a1"
access ok
root@j9 ~#

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

content_by_lua

content阶段,<u>注意在同一个Location中不要和其他content阶段指令一起使用,比如proxy_pass。</u> 例子:略

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

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

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了。

© 著作权归作者所有

共有 人打赏支持
我是王雪梨
粉丝 7
博文 8
码字总数 32297
作品 0
东城
运营/编辑
【云周刊】第167期:如何将深度学习应用在广告、推荐及搜索业务?阿里妈妈实践案例解读!

本期头条 如何将深度学习应用在广告、推荐及搜索业务?阿里妈妈实践案例解读! 互联网数据的特点是规模大,转化成机器学习的语言就是维度特别高,样本特别多,另外互联网数据内部也有丰富的内...

场景研读 ⋅ 04/19 ⋅ 0

听说Tech Insight在北京城又火了一把?

即将在12月19日国家会议中心举办的Tech Insight是2017年最后一场,也是全年的压轴之作。此前Tech Insight已在北京、上海、广州、深圳、成都等地多次举办,场场爆满。本次北京站结合了历届Tec...

仙游 ⋅ 2017/12/14 ⋅ 0

LiveVideoStackCon 2017 打造最专业的音视频技术大会

  【IT168 评论】10个年头前――2007年1月,乔布斯在Macworld大会上公开了第一代iPhone,时至今日这台电子设备在全球各地已经售卖出超过12亿台。随处可用的WiFi、3G、4G网络,让iPhone成为...

it168网站 ⋅ 2017/08/29 ⋅ 0

飞天技术汇“2018云栖大会·上海峰会”专场,等你加入

智能制造是中国传统企业面临的重大课题,如何正确处理企业智能转型过程中出现的各种问题,如何顺利完成传统制造业的数字化革命,云计算的智能化解决方案将为观众提供全新的思路。 在飞天技术...

云攻略小攻 ⋅ 05/18 ⋅ 0

阿里云 2017 值得关注的产品和事儿

前言 随着 2017年 的结束,云计算即将进入第二个十年,云计算产业格局也将更加风起云涌,各家云计算都再不断的优化明星产品并不断发布新产品,作为国际 3A 的阿里云也是一直在发展和进步,阿...

妙正灰 ⋅ 01/03 ⋅ 0

【企业换新装】-- 如何为企业快速设计高可用的阿里云架构

前言  近些年阿里云可以说是非常火爆的一个话题,相信熟悉阿里云产品的朋友都知道阿里云的这句代言: “阿里云让高可用更简单”  实际是确实是这么回事,而近几年也越来越多的企业都在普及...

甘兵 ⋅ 04/15 ⋅ 0

上海云栖:金融政企行业的CDN最佳实践

在刚刚结束的上海云栖大会飞天技术汇分论坛上,阿里云视频云产品架构师罗小飞进行了《阿里云CDN——面向金融政企的CDN最佳实践》主题分享,为上海的嘉宾介绍CDN的解决方案与技术服务体系。 ...

樰篱 ⋅ 06/14 ⋅ 0

飞天技术汇“2018云栖大会·武汉峰会”专场,等你加入

云栖大会是由阿里巴巴集团主办的全球顶级科技大会,汇聚时代最强大脑,描绘新技术发展趋势和蓝图,展现云计算、大数据、人工智能等蓬勃发展的科技生态全景。 智能制造是中国传统企业面临的重...

云攻略小攻 ⋅ 05/07 ⋅ 0

飞天技术汇“2018云栖大会·南京峰会”专场,等你加入

云栖大会是由阿里巴巴集团主办的全球顶级科技大会,汇聚时代最强大脑,描绘新技术发展趋势和蓝图,展现云计算、大数据、人工智能等蓬勃发展的科技生态全景。 智能制造是中国传统企业面临的重...

云攻略小攻 ⋅ 04/18 ⋅ 0

干货帖 | 阿里云云安全ACP认证考试攻略来袭…

号外!号外!云安全专业认证考试的经验分享来啦,除了烧高香,拜大佛,尊考神,我们还要做些什么实实在在的准备呢?我们来看一看~ Step 1. 了解考试考什么 一、考试简介 阿里云云安全专业认证...

ftp4oss ⋅ 02/28 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

火狐浏览器各版本下载及插件httprequest

各版本下载地址:http://ftp.mozilla.org/pub/mozilla.org//firefox/releases/ httprequest插件截至57版本可用

xiaoge2016 ⋅ 22分钟前 ⋅ 0

Java学习路径及练手项目合集

Java学习路径及练手项目合集

颖伙虫 ⋅ 37分钟前 ⋅ 0

Docker系列教程28-实战:使用Docker Compose运行ELK

原文:http://www.itmuch.com/docker/28-docker-compose-in-action-elk/,转载请说明出处。 ElasticSearch【存储】 Logtash【日志聚合器】 Kibana【界面】 答案: version: '2'services: ...

周立_ITMuch ⋅ 今天 ⋅ 0

使用快嘉sdkg极速搭建接口模拟系统

在具体项目研发过程中,一旦前后端双方约定好接口,前端和app同事就会希望后台同事可以尽快提供可供对接的接口方便调试,而对后台同事来说定好接口还仅是个开始、设计流程,实现业务逻辑,编...

fastjrun ⋅ 今天 ⋅ 0

PXE/KickStart 无人值守安装

导言 作为中小公司的运维,经常会遇到一些机械式的重复工作,例如:有时公司同时上线几十甚至上百台服务器,而且需要我们在短时间内完成系统安装。 常规的办法有什么? 光盘安装系统 ===> 一...

kangvcar ⋅ 昨天 ⋅ 0

使用Puppeteer撸一个爬虫

Puppeteer是什么 puppeteer是谷歌chrome团队官方开发的一个无界面(Headless)chrome工具。Chrome Headless将成为web应用自动化测试的行业标杆。所以我们很有必要来了解一下它。所谓的无头浏...

小草先森 ⋅ 昨天 ⋅ 0

Java Done Right

* 表示难度较大或理论性较强。 ** 表示难度更大或理论性更强。 【Java语言本身】 基础语法,面向对象,顺序编程,并发编程,网络编程,泛型,注解,lambda(Java8),module(Java9),var(...

风华神使 ⋅ 昨天 ⋅ 0

Linux系统日志

linux 系统日志 /var/log/messages /etc/logrotate.conf 日志切割配置文件 https://my.oschina.net/u/2000675/blog/908189 logrotate 使用详解 dmesg 命令 /var/log/dmesg 日志 last命令,调......

Linux学习笔记 ⋅ 昨天 ⋅ 0

MVC——统一报文格式的异常处理响应

在我们写controller层的时候,常常会有这样的困惑,如果需要返回一个数据是,可能为了统一回去构造一个类似下列的数据格式: { status:true, msg:"保存成功!", data:[]} 而且在写...

alexzhu592 ⋅ 昨天 ⋅ 0

android -------- 打开本地浏览器或指定浏览器加载,打电话,打开第三方app

开发中常常有打开本地浏览器加载url或者指定浏览器加载, 还有打开第三方app, 如 打开高德地图 百度地图等 在Android程序中我们可以通过发送隐式Intent来启动系统默认的浏览器。 如果手机本身...

切切歆语 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部