第二章 OpenResty(Nginx+Lua)开发入门
Nginx入門
本文目的是學習Nginx+Lua開發,對于Nginx基本知識可以參考如下文章:
nginx啟動、關閉、重啟
http://www.cnblogs.com/derekchen/archive/2011/02/17/1957209.html
agentzh 的 Nginx 教程
http://openresty.org/download/agentzh-nginx-tutorials-zhcn.html
Nginx+Lua入門
http://17173ops.com/2013/11/01/17173-ngx-lua-manual.shtml
nginx 配置指令的執行順序
http://zhongfox.github.io/blog/server/2013/05/15/nginx-exec-order/
nginx與lua的執行順序和步驟說明
http://www.mrhaoting.com/?p=157
Nginx配置文件nginx.conf中文詳解
http://www.ha97.com/5194.html
Tengine的Nginx開發從入門到精通
http://tengine.taobao.org/book/
官方文檔
http://wiki.nginx.org/Configuration
?
Lua入門
本文目的是學習Nginx+Lua開發,對于Lua基本知識可以參考如下文章:
Lua簡明教程
http://coolshell.cn/articles/10739.html
lua在線lua學習教程
http://book.luaer.cn/
Lua 5.1 參考手冊
http://www.codingnow.com/2000/download/lua_manual.html
Lua5.3 參考手冊
http://cloudwu.github.io/lua53doc/
Nginx Lua API
和一般的Web Server類似,我們需要接收請求、處理并輸出響應。而對于請求我們需要獲取如請求參數、請求頭、Body體等信息;而對于處理就是調用相應的Lua代碼即可;輸出響應需要進行響應狀態碼、響應頭和響應內容體的輸出。因此我們從如上幾個點出發即可。
?
接收請求
1、example.conf配置文件?
Java代碼??2、test_request.lua?
Java代碼??ngx.var?: nginx變量,如果要賦值如ngx.var.b = 2,此變量必須提前聲明;另外對于nginx location中使用正則捕獲的捕獲組可以使用ngx.var[捕獲組數字]獲取;
ngx.req.get_headers:獲取請求頭,默認只獲取前100,如果想要獲取所以可以調用ngx.req.get_headers(0);獲取帶中劃線的請求頭時請使用如headers.user_agent這種方式;如果一個請求頭有多個值,則返回的是table;
ngx.req.get_uri_args:獲取url請求參數,其用法和get_headers類似;
ngx.req.get_post_args:獲取post請求內容體,其用法和get_headers類似,但是必須提前調用ngx.req.read_body()來讀取body體(也可以選擇在nginx配置文件使用lua_need_request_body on;開啟讀取body體,但是官方不推薦);
ngx.req.raw_header:未解析的請求頭字符串;
ngx.req.get_body_data:為解析的請求body體內容字符串。
?
如上方法處理一般的請求基本夠用了。另外在讀取post內容體時根據實際情況設置client_body_buffer_size和client_max_body_size來保證內容在內存而不是在文件中。
?
使用如下腳本測試
Java代碼???
輸出響應?
1.1、example.conf配置文件
Java代碼??1.2、test_response_1.lua?
Java代碼??ngx.header:輸出響應頭;
ngx.print:輸出響應內容體;
ngx.say:通ngx.print,但是會最后輸出一個換行符;
ngx.exit:指定狀態碼退出。
?
2.1、example.conf配置文件
Java代碼???
2.2、test_response_2.lua
Java代碼??ngx.redirect:重定向;?
?
ngx.status=狀態碼,設置響應的狀態碼;ngx.resp.get_headers()獲取設置的響應狀態碼;ngx.send_headers()發送響應狀態碼,當調用ngx.say/ngx.print時自動發送響應狀態碼;可以通過ngx.headers_sent=true判斷是否發送了響應狀態碼。
?
其他API
1、example.conf配置文件
Java代碼???
2、test_other.lua
Java代碼???
ngx.escape_uri/ngx.unescape_uri?: uri編碼解碼;
ngx.encode_args/ngx.decode_args:參數編碼解碼;
ngx.encode_base64/ngx.decode_base64:BASE64編碼解碼;
ngx.re.match:nginx正則表達式匹配;
?
更多Nginx Lua API請參考?http://wiki.nginx.org/HttpLuaModule#Nginx_API_for_Lua。
?
Nginx全局內存
使用過如Java的朋友可能知道如Ehcache等這種進程內本地緩存,Nginx是一個Master進程多個Worker進程的工作方式,因此我們可能需要在多個Worker進程中共享數據,那么此時就可以使用ngx.shared.DICT來實現全局內存共享。
?
1、首先在nginx.conf的http部分分配內存大小
Java代碼???
2、example.conf配置文件
Java代碼??3、?test_lua_shared_dict.lua
Java代碼??更多API請參考http://wiki.nginx.org/HttpLuaModule#ngx.shared.DICT。?
?
?
到此基本的Nginx Lua API就學完了,對于請求處理和輸出響應如上介紹的API完全夠用了,更多API請參考官方文檔。
?
Nginx Lua模塊指令
Nginx共11個處理階段,而相應的處理階段是可以做插入式處理,即可插拔式架構;另外指令可以在http、server、server if、location、location if幾個范圍進行配置:
| 指令 | 所處處理階段 | 使用范圍 | 解釋 |
| 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 if | rrewrite階段處理,可以實現復雜的轉發/重定向邏輯; |
| 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 if | log階段處理,比如記錄訪問量/統計平均響應時間 |
?
更詳細的解釋請參考http://wiki.nginx.org/HttpLuaModule#Directives。如上指令很多并不常用,因此我們只拿其中的一部分做演示。
?
init_by_lua
每次Nginx重新加載配置時執行,可以用它來完成一些耗時模塊的加載,或者初始化一些全局配置;在Master進程創建Worker進程時,此指令中加載的全局變量會進行Copy-OnWrite,即會復制到所有全局變量到Worker進程。
?
1、nginx.conf配置文件中的http部分添加如下代碼
Java代碼????
2、init.lua
Java代碼???
3、test.lua
Java代碼?????
4、訪問如http://192.168.1.2/lua 會發現全局變量一直不變,而共享內存一直遞增
global variable : 2 , shared memory : 8 hello world?
?
另外注意一定在生產環境開啟lua_code_cache,否則每個請求都會創建Lua VM實例。
?
init_worker_by_lua
用于啟動一些定時任務,比如心跳檢查,定時拉取服務器配置等等;此處的任務是跟Worker進程數量有關系的,比如有2個Worker進程那么就會啟動兩個完全一樣的定時任務。
?
1、nginx.conf配置文件中的http部分添加如下代碼
Java代碼????
2、init_worker.lua
Java代碼??ngx.timer.at:延時調用相應的回調方法;ngx.timer.at(秒單位延時,回調函數,回調函數的參數列表);可以將延時設置為0即得到一個立即執行的任務,任務不會在當前請求中執行不會阻塞當前請求,而是在一個輕量級線程中執行。
?
另外根據實際情況設置如下指令
lua_max_pending_timers 1024; ?#最大等待任務數
lua_max_running_timers 256; ? ?#最大同時運行任務數
?
?
set_by_lua?
設置nginx變量,我們用的set指令即使配合if指令也很難實現負責的賦值邏輯;
?
1.1、example.conf配置文件
Java代碼??set_by_lua_file:語法set_by_lua_file $var lua_file arg1 arg2...; 在lua代碼中可以實現所有復雜的邏輯,但是要執行速度很快,不要阻塞;
?
1.2、test_set_1.lua
Java代碼??得到請求參數進行相加然后返回。
?
訪問如http://192.168.1.2/lua_set_1?i=1&j=10進行測試。 如果我們用純set指令是無法實現的。
?
再舉個實際例子,我們實際工作時經常涉及到網站改版,有時候需要新老并存,或者切一部分流量到新版
?
2.1、首先在example.conf中使用map指令來映射host到指定nginx變量,方便我們測試
Java代碼??如綁定hosts
192.168.1.2 item.jd.com;
192.168.1.2 item2014.jd.com;
?
此時我們想訪問item2014.jd.com時訪問新版,那么我們可以簡單的使用如
Java代碼???
但是我們想把商品編號為為8位(比如品類為圖書的)沒有改版完成,需要按照相應規則跳轉到老版,但是其他的到新版;雖然使用if指令能實現,但是比較麻煩,基本需要這樣
Java代碼??以上規則還是比較簡單的,如果涉及到更復雜的多重if/else或嵌套if/else實現起來就更痛苦了,可能需要到后端去做了;此時我們就可以借助lua了:
Java代碼????
?rewrite_by_lua?
執行內部URL重寫或者外部重定向,典型的如偽靜態化的URL重寫。其默認執行在rewrite處理階段的最后。
?
1.1、example.conf配置文件
Java代碼???
1.2、test_rewrite_1.lua
Java代碼??當我們請求http://192.168.1.2/lua_rewrite_1時發現沒有跳轉,而請求http://192.168.1.2/lua_rewrite_1?jump=1時發現跳轉到京東首頁了。 此處需要301/302跳轉根據自己需求定義。
?
2.1、example.conf配置文件
Java代碼???
2.2、test_rewrite_2.lua
Java代碼??ngx.req.set_uri(uri, false):可以內部重寫uri(可以帶參數),等價于 rewrite ^ /lua_rewrite_3;通過配合if/else可以實現?rewrite ^ /lua_rewrite_3 break;這種功能;此處兩者都是location內部url重寫,不會重新發起新的location匹配;
ngx.req.set_uri_args:重寫請求參數,可以是字符串(a=1&b=2)也可以是table;
?
訪問如http://192.168.1.2/lua_rewrite_2?jump=0時得到響應
rewrite2 uri : /lua_rewrite_2, a :
?
訪問如http://192.168.1.2/lua_rewrite_2?jump=1時得到響應
rewrite2 uri : /lua_rewrite_4, a : 1
?
3.1、example.conf配置文件
Java代碼???
3.2、test_rewrite_3.lua
Java代碼??ngx.req.set_uri(uri, true):可以內部重寫uri,即會發起新的匹配location請求,等價于 rewrite ^ /lua_rewrite_4 last;此處看error log是看不到我們記錄的log。
?
所以請求如http://192.168.1.2/lua_rewrite_3?jump=1會到新的location中得到響應,此處沒有/lua_rewrite_4,所以匹配到/lua請求,得到類似如下的響應
global variable : 2 , shared memory : 1 hello world
?
即
rewrite ^ /lua_rewrite_3; ? ? ? ? ? ? ? ? 等價于 ?ngx.req.set_uri("/lua_rewrite_3", false);
rewrite ^ /lua_rewrite_3 break; ? ? ? 等價于 ?ngx.req.set_uri("/lua_rewrite_3", false); 加 if/else判斷/break/return
rewrite ^ /lua_rewrite_4 last; ? ? ? ? ? 等價于 ?ngx.req.set_uri("/lua_rewrite_4", true);
?
注意,在使用rewrite_by_lua時,開啟rewrite_log on;后也看不到相應的rewrite log。
?
access_by_lua?
用于訪問控制,比如我們只允許內網ip訪問,可以使用如下形式
Java代碼???
1.1、example.conf配置文件
Java代碼???
?1.2、test_access.lua
Java代碼??即如果訪問如http://192.168.1.2/lua_access?token=234將得到403 Forbidden的響應。這樣我們可以根據如cookie/用戶token來決定是否有訪問權限。
?
?
content_by_lua???
此指令之前已經用過了,此處就不講解了。
?
另外在使用PCRE進行正則匹配時需要注意正則的寫法,具體規則請參考http://wiki.nginx.org/HttpLuaModule中的Special PCRE Sequences部分。還有其他的注意事項也請閱讀官方文檔。
來源:http://jinnianshilongnian.iteye.com/blog/2186448
總結
以上是生活随笔為你收集整理的第二章 OpenResty(Nginx+Lua)开发入门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 碳酸氢钙(说一说碳酸氢钙的简介)
- 下一篇: 第三章 Redis/SSDB+Twemp