當前位置: 妍妍網 > 碼農

OpenResty實戰系列 | Nginx Lua API 介面開發

2024-07-16碼農

概述

OpenResty 為開發者提供了一系列強大的API,這些API使得Lua指令碼能夠與Nginx緊密互動,從而高效地執行多種Web伺服器任務。在處理Web伺服器的核心工作流程中,主要包括三個環節:接收請求、處理請求以及輸出響應。在接收請求時,我們能夠獲取到請求參數、請求頭部以及請求體等關鍵資訊。處理請求則涉及執行特定的Lua程式碼邏輯。至於輸出響應,則需要設定響應狀態碼、自訂響應頭部以及構造響應內容體。

在Web開發的典型流程中,接收請求、處理請求並輸出響應是三個核心環節。OpenResty以其獨特的方式最佳化了這些環節的處理過程:

  1. 接收請求 :OpenResty允許Lua指令碼直接存取到請求的各個組成部份,包括但不限於請求參數(無論是URL中的查詢參數還是POST請求體中的欄位)、請求頭資訊以及完整的請求體內容。這種直接存取能力讓開發者能夠輕松解析並理解客戶端的請求意圖,為後續的處理邏輯提供堅實的數據基礎。

  2. 處理請求 :一旦請求被接收並解析,OpenResty便透過其提供的Lua API呼叫相應的Lua程式碼來處理這些請求。得益於Lua語言的輕量級和高效性,以及OpenResty對Nginx內部機制的深度整合,這一處理過程既快速又靈活。開發者可以編寫復雜的業務邏輯,呼叫外部服務,執行資料庫操作等,以滿足各種業務需求。

  3. 輸出響應 :在處理完請求後,OpenResty同樣支持透過Lua指令碼靈活地構建並輸出響應。這包括設定響應狀態碼(如200 OK、404 Not Found等),添加或修改響應頭資訊(如Content-Type、Set-Cookie等),以及發送響應體內容。透過精細控制響應的各個方面,開發者能夠確保客戶端接收到準確、清晰且符合預期的響應。

接收請求

openresty.tinywan.com.conf 配置檔

server {
listen 80;
server_name openresty.tinywan.com;
location ~ /lua_request/(\d+)/(\d+) {
default_type "text/html";
lua_code_cache off;
# 設定nginx變量
set$a$1;
set$b$host;
# nginx內容處理
content_by_lua_file conf/lua/request_test.lua;
# 內容體處理完成後呼叫
echo_after_body "[x] 內容體處理完成後呼叫 ngx.var.b : $b";
}
}

request_test.lua 檔程式碼

--[[--------------------------------------------------------
* | Copyright (C) Shaobo Wan (Tinywan)
* | Origin: 開源技術小棧
* |-------------------------------------------------------- 
--]]

--接受Nginx變量 ngx.var 存取Nginx變量,例如客戶端IP地址、請求URI等。
local var = ngx.var
ngx.say("[x] ngx.var.a : ", var.a)
ngx.say("[x] ngx.var.b : ", var.b)
ngx.say("[x] ngx.var[2] : ", var[2])
ngx.var.b = "Tinywan Openresty";
ngx.say("\r\n")
--請求頭
local headers = ngx.req.get_headers()
ngx.say("[x] headers begin")
ngx.say("[x] Host : ", headers["Host"])
ngx.say("[x] user-agent1 : ", headers["user-agent"])
ngx.say("[x] user-agent2 : ", headers.user_agent)
for k,v inpairs(headers) do
iftype(v) == "table"then
ngx.say(k, " : "table.concat(v, ","))
else
ngx.say(k, " : ", v)
end
end
ngx.say("[x] headers end")
ngx.say("\r\n"
--get請求uri參數
ngx.say("[x] uri args begin")
local uri_args = ngx.req.get_uri_args()
for k, v inpairs(uri_args) do
iftype(v) == "table"then
ngx.say(k, " : "table.concat(v, ", "))
else
ngx.say(k, ": ", v)
end
end
ngx.say("[x] uri args end")
ngx.say("\r\n"
--post請求參數
ngx.req.read_body()
ngx.say("[x] post args begin")
local post_args = ngx.req.get_post_args()
for k, v inpairs(post_args) do
iftype(v) == "table"then
ngx.say(k, " : "table.concat(v, ", "))
else
ngx.say(k, ": ", v)
end
end
ngx.say("[x] post args end")
ngx.say("\r\n"
--請求的http協定版本
ngx.say("[x] ngx.req.http_version : ", ngx.req.http_version())
--請求方法
ngx.say("[x] ngx.req.get_method : ", ngx.req.get_method())
--原始的請求頭內容
ngx.say("[x] ngx.req.raw_header : ", ngx.req.raw_header())
--請求的body內容體
ngx.say("[x] ngx.req.get_body_data() : ", ngx.req.get_body_data())




透過curl指令碼測試請求打印結果

$ curl -i -H "Content-Type:application/json" -X POST -d '{"name":"ShaoBoWan","age":24}' http://openresty.tinywan.com/lua_request/2024/12/?name=Tinywan&schoole=Ggoogle
[11264
HTTP/1.1200 OK
Server: openresty/1.17.8.2
Date: Tue, 16 Jul 202400:52:36 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
[x] ngx.var.a : 2024
[x] ngx.var.b : openresty.tinywan.com
[x] ngx.var[2] : 12

[x] headers begin
[x] Host : openresty.tinywan.com
[x] user-agent1 : curl/7.70.0
[x] user-agent2 : curl/7.70.0
host : openresty.tinywan.com
content-type : application/json
user-agent : curl/7.70.0
accept : */*
content-length : 29
[x] headers end

[x] uri args begin
name: Tinywan
[x] uri args end

[x] post args begin
{"name":"ShaoBoWan","age":24}: true
[x] post args end

[x] ngx.req.http_version : 1.1
[x] ngx.req.get_method : POST
[x] ngx.req.raw_header : POST /lua_request/2024/12/?name=Tinywan HTTP/1.1
Host: openresty.tinywan.com
User-Agent: curl/7.70.0
Accept: */*
Content-Type:application/json
Content-Length: 29

[x] ngx.req.get_body_data() : {"name":"ShaoBoWan","age":24}
[x] 內容體處理完成後呼叫 ngx.var.b :Tinywan Openresty
[1]+ Done curl -i -H "Content-Type:application/json" -X POST -d '{"name":"ShaoBoWan","age":24}' http://openresty.tinywan.com/lua\_request/2024/12/?name=Tinywan





  • ngx.var :nginx變量,如果要賦值如 ngx.var.b = 2 ,此變量必須提前聲明;另外對於``nginx location中使用正則捕獲的捕獲組可以使用 ngx.var[捕獲組數位] 獲取;

  • ngx.req.get_headers :獲取請求頭,預設只獲取前100,如果想要獲取所以可以呼叫 ngx.req.get_headers(0) ;獲取帶中劃線的請求頭時請使用如 headers.user_agent 這種方式;如果一個請求頭有多個值,則返回的是lua 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 體內容字串。

  • 處理請求

    openresty.tinywan.com.conf 配置檔

    location /lua_response_02 {
    default_type "text/html";
    lua_code_cache off; 
    content_by_lua_file conf/lua/response_test_02.lua;
    }

    response_test_02.lua 指令碼程式碼

    ngx.redirect("https://www.tinywan.com"302)

    透過curl指令碼測試請求打印結果

    $ curl -i http://openresty.tinywan.com/lua_response_02
    HTTP/1.1302 Moved Temporarily
    Server: openresty/1.17.8.2
    Date: Tue, 16 Jul 202401:13:26 GMT
    Content-Type: text/html
    Content-Length: 151
    Connection: keep-alive
    Location: https://www.tinywan.com
    <html>
    <head><title>302 Found</title></head>
    <body>
    <center><h1>302 Found</h1></center>
    <hr><center>openresty/1.17.8.2</center>
    </
    body>
    </html>

  • ngx.status=狀態碼 ,設定響應的狀態碼;

  • ngx.resp.get_headers() 獲取設定的響應狀態碼;

  • ngx.send_headers() 發送響應狀態碼,當呼叫 ngx.say/ngx.print 時自動發送響應狀態碼;可以透過 ngx.headers_sent=true 判斷是否發送了響應狀態碼。

  • openresty.tinywan.com.conf 配置檔

    location /lua_response_03 {
    default_type "text/html";
    lua_code_cache off; 
    content_by_lua_file conf/lua/response_test_03.lua;
    }

    response_test_03.lua 指令碼程式碼

    --[[---------------------------------------------------------
    * | Copyright (C) Shaobo Wan (Tinywan)
    * | Origin: 開源技術小棧
    * |-----------------------------------------------------------
    --]]

    --未經解碼的請求uri
    local request_uri = ngx.var.request_uri;
    ngx.say("[x] request_uri : ", request_uri);
    --解碼
    ngx.say("[x] decode request_uri : ", ngx.unescape_uri(request_uri));
    --MD5
    ngx.say("[x] ngx.md5 : ", ngx.md5("123"))
    --http time
    ngx.say("[x] ngx.http_time : ", ngx.http_time(ngx.time()))

    透過curl指令碼測試請求打印結果

    $ curl -i http://openresty.tinywan.com/lua_response_03
    HTTP/1.1200 OK
    Server: openresty/1.17.8.2
    Date: Tue, 16 Jul 202401:38:43 GMT
    Content-Type: text/html; charset=utf-8
    Transfer-Encoding: chunked
    Connection: keep-alive
    Vary: Accept-Encoding
    [x] request_uri : /lua_response_03
    [x] decode request_uri : /lua_response_03
    [x] ngx.md5 : 202cb962ac59075b964b07152d234b70
    [x] ngx.http_time : Tue, 16 Jul 202401:38:43 GMT

    如果存取出現 500 Internal Server Error 請透過nginx錯誤日誌排查,下面錯誤表示缺少一個結束符 ;

    [error7#7: *2 failed to load external Lua file 
    "/usr/local/openresty/nginx/conf/lua/response_test_03.lua"
    /usr/local/openresty/nginx/conf/lua/response_test_03.lua:13: unfinished string near '") '
    client: 172.18.0.1
    server: openresty.tinywan.com, 
    request: "GET /lua_response_03 HTTP/1.1"
    host: "openresty.tinywan.com"

    輸出響應

    openresty.tinywan.com.conf 配置檔

    server {
    listen 80;
    server_name openresty.tinywan.com;
    location /lua_response_01 {
    default_type "text/html";
    lua_code_cache off; 
    content_by_lua_file conf/lua/response_test_01.lua;
    }
    }

    response_test_01.lua 指令碼程式碼

    --[[---------------------------------------------------------
    * | Copyright (C) Shaobo Wan (Tinywan)
    * | Origin: 開源技術小棧
    * |-----------------------------------------------------------
    --]]

    --寫響應頭
    ngx.header.age = "24"
    --多個響應頭可以使用table
    ngx.header.name = {"Tinywan""ShaoBoWan"}
    --輸出響應
    ngx.say("[x] age""name")
    ngx.print("[x] age""name")
    --200狀態碼結束
    return ngx.exit(200)

    透過curl指令碼測試請求打印結果

    $ curl -i http://openresty.tinywan.com/lua_response_01

    HTTP/1.1200 OK
    Server: openresty/1.17.8.2
    Date: Tue, 16 Jul 202401:09:51 GMT
    Content-Type: text/html; charset=utf-8
    Transfer-Encoding: chunked
    Connection: keep-alive
    Vary: Accept-Encoding
    age: 24
    name: Tinywan
    name: ShaoBoWan
    [x] agename
    [x] agename

  • ngx.header :輸出響應頭;

  • ngx.print :輸出響應內容體;

  • ngx.say :通 ngx.print ,但是會最後輸出一個換行符;

  • ngx.exit :指定狀態碼結束。

  • Nginx全域記憶體

    Nginx是一個Master行程多個Worker行程的工作方式,因此我們可能需要在多個Worker行程中共享數據。對於全域記憶體的配置,Nginx提供了 lua_shared_dict 指令,允許在Nginx的http部份分配記憶體大小,定義一塊共享記憶體空間,所有worker行程都可見 6。這種共享記憶體機制類似於Java中的Ehcache行程內本地緩存,允許在多個Worker行程間共享數據 6。例如,可以使用以下語法分配10MB的共享記憶體:

    http {
    # 共享全域變量,在所有worker間共享
    lua_shared_dict shared_resty_data 1m;
    ...
    server {
    listen 80;
    server_name openresty.tinywan.com;
    location /lua_shared_dict {
    default_type "text/html";
    lua_code_cache off; 
    content_by_lua_file conf/lua/lua_shared_dict_test.lua;
    }
    }
    }

    在使用共享記憶體時,可以透過Lua程式碼進行操作,例如獲取、設定、刪除共享記憶體中的鍵值對 6。例如,使用以下Lua程式碼可以獲取和設定共享記憶體中的值。 lua_shared_dict_test.lua 指令碼檔

    --1、獲取全域共享記憶體變量
    local resty_shared_data = ngx.shared.shared_resty_data
    --2、獲取字典值
    local i = resty_shared_data:get("i")
    if not i then
    i = 1
    --3、惰性賦值
    resty_shared_data:set("i", i)
    ngx.say("[x] lazy set i ", i)
    end
    --4、遞增
    i = resty_shared_data:incr("i", 1)
    ngx.say("[x] i = ", i)

    此外,還有 get_stale safe_set add safe_add replace 等方法,用於處理共享記憶體中的數據,包括處理過期鍵和避免記憶體不足時的強制刪除操作。

    Nginx全域變量是儲存在伺服器行程記憶體中的數據,用於在配置和執行時提供各種資訊,可以分為常量變量、內建變量和自訂變量 5。全域變量的使用可以提高配置的靈活性,簡化管理任務,並提供對伺服器執行狀況的深入了解。

    請參考 http://wiki.nginx.org/HttpLuaModule#ngx.shared.DICT

    相關閱讀系列