【技術乾貨】聽阿里雲CDN安防技術專家金九講tengine+lua開發

小資一夏發表於2017-08-11

一、介紹  

二、安裝  

三、執行  

四、開發  

 

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下的業務邏輯。

[]()



2. 安裝


2.1、LuaJIT



wget -c 
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 
cd tengine
./configure --prefix=/opt/tengine --with-http_lua_module
make
make install




如果是原生nginx的話,得自行下載lua模組程式碼:

wget 
tar xvf nginx-1.7.8.tar.gz
cd nginx-1.7.8
mkdir modules
cd modules
git clone .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 
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("[]()", 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("", 301)                         
            ';                                                                     
        } 
    }




測試結果:


root@j9 ~# curl "" -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 "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: 
<!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.
<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 "var/aaaa/bbbb?id=22" -H "Host: "
$a: aaa
$b: 
$host: 
$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 "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 "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 ""
i: 2
root@j9 ~# curl ""
i: 3
root@j9 ~#


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 階段處理,比如記錄訪問量/統計平均響應時間


更詳細的解釋請參考官網:



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 '\{"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 ""
a + b = 33, a - b = -11, 2 * c = 176



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 ""
a: aa
root@j9 ~# curl ""
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 ""             
<!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!
<table>

<td>URL:
<td>


<td>Server:
<td>j9


<td>Date:
<td>2015/10/27 16:47:20


<hr/>Powered by Tengine/2.2.0</body>
</html>
root@j9 ~# echo -n alicdnj9 | md5sum            
50652c84270d22210593318f5d3016a1  -
root@j9 ~# curl ""
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 "" -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 "" -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 ""
a: 1, b: 1 counter: 1
root@j9 ~# curl ""
a: 1, b: 1 counter: 2
root@j9 ~# curl ""
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 
    }
    location /get {
        if ($arg_tag = "1") {
            return 302 "";
        }
        return 200 "ok";
    }
}
server {
    listen 8282;
    default_type "text/html";
    location / {
        return 200 "redirect ok, args: $args";
    }
}



測試結果:

root@j9 ~# curl "" -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 "" -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 ""
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 ""
this is @cc location, cc: 5
root@j9 ~# curl ""            
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了。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/29779867/viewspace-2143408/,如需轉載,請註明出處,否則將追究法律責任。

相關文章