1. 程式人生 > 實用技巧 >OpenResty入門之使用Lua開發Nginx外掛

OpenResty入門之使用Lua開發Nginx外掛

記住一點:nginx配置檔案很多坑來源自你的空格少了或多了。

OpenResty

OpenResty 是一個基於 Nginx 與 Lua 的高效能 Web 平臺,其內部集成了大量精良的 Lua 庫、第三方模組以及大多數的依賴項。用於方便地搭建能夠處理超高併發、擴充套件性極高的動態 Web 應用、Web 服務和動態閘道器。

OpenResty 通過匯聚各種設計精良的 Nginx 模組(主要由 OpenResty 團隊自主開發),從而將 Nginx 有效地變成一個強大的通用 Web 應用平臺。這樣,Web 開發人員和系統工程師可以使用 Lua 指令碼語言調動 Nginx 支援的各種 C 以及 Lua 模組,快速構造出足以勝任 10K 乃至 1000K 以上單機併發連線的高效能 Web 應用系統。

OpenResty 的目標是讓你的Web服務直接跑在 Nginx 服務內部,充分利用 Nginx 的非阻塞 I/O 模型,不僅僅對 HTTP 客戶端請求,甚至於對遠端後端諸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都進行一致的高效能響應。

1.Centos下載安裝

如果你的系統是 Centos 或 RedHat 可以使用以下命令:

yum install readline-devel pcre-devel openssl-devel

接下我們可以在官方(https://openresty.org/cn/)下載最新的 OpenResty 原始碼包並解壓編譯安裝:

wget https://openresty.org/download/ngx_openresty-1.9.7.1.tar.gz   # 下載
tar xzvf ngx_openresty-1.9.7.1.tar.gz # 解壓
cd ngx_openresty-1.9.7.1/
./configure
make
make install

預設情況下程式會被安裝到 /usr/local/openresty 目錄,你可以使用 ./configure --help 檢視更多的配置選項。

2.HelloWorld例項

安裝成功後,我們就可以使用 openresty 直接輸出 html 頁面。

首先我們可以建立一個工作目錄:

mkdir /home/www
cd /home/www/
mkdir logs/ conf/

其中 logs 目錄用於存放日誌,conf 用於存放配置檔案。

接著,我們在 conf 目錄下建立一個 nginx.conf 檔案 程式碼如下:

worker_processes  1;
error_log logs/error.log;
events {
worker_connections 1024;
}
http {
server {
listen 9000;
location / {
default_type text/html;
content_by_lua '
ngx.say("<p>Hello, World!</p>")
';
}
}
}

如果你熟悉 nginx 的配置,應該對以上程式碼就很熟悉。這裡我們將 html 程式碼直接寫在了配置檔案中。

啟動 openresty

預設情況下 openresty 安裝在 /usr/local/openresty 目錄中,啟動命令為:

/usr/local/openresty/nginx/sbin/nginx -p /home/www/ -c conf/nginx.conf

如果沒有任何輸出,說明啟動成功,-p 指定我們的專案目錄,-c 指定配置檔案。

接下來我們可以使用 curl 來測試是否能夠正常範圍:

curl http://localhost:9000/

輸出結果為:

<p>Hello, World!</p>

3.呼叫Lua指令碼檔案

在 HelloWorld 例項中,我們直接在 nginx.conf 中寫Lua指令碼,很多時候,Lua指令碼是一個檔案。下面演示使用 content_by_lua_file 指令呼叫Lua指令碼檔案。

在conf資料夾下建立helloworld.lua:

ngx.say("<p>Hello, World!</p>")

修改你的 nginx.conf 檔案內容為:

worker_processes  1;
error_log logs/error.log;
events {
worker_connections 1024;
}
http {
server {
listen 9000;
location / {
default_type text/html;
content_by_lua_file 'conf/helloworld.lua';
}
}
}

停止已啟動的nginx程序:

killall -9 nginx

啟動nginx程序:

/usr/local/openresty/nginx/sbin/nginx -p /home/www/ -c conf/nginx.conf

接下來我們可以使用 curl 來測試是否能夠正常範圍:

curl 'localhost:9000'

輸出結果為:

<p>Hello, World!</p>

4.set_by_lua指令

使用 set_by_lua 指定可以用類似呼叫函式的形式去呼叫Lua指令碼。語法:

set_by_lua $res <lua-script-str> [$arg1 $arg2 ...]

修改你的conf/nginx.conf檔案:

worker_processes  1;
error_log logs/error.log;
events {
worker_connections 1024;
}
http {
server {
listen 9000;
location / {
default_type text/html;
set_by_lua $res '
local a = tonumber(ngx.arg[1])
local b = tonumber(ngx.arg[2])
return a+b' $arg_a $arg_b;
echo $res;
}
}
}

停止已啟動的nginx程序,命令:

killall -9 nginx

啟動nginx程序:

/usr/local/openresty/nginx/sbin/nginx -p /home/www/ -c conf/nginx.conf

接下來我們可以使用 curl 來測試是否能夠正常範圍:

curl 'localhost:9000/?a=2&b=5'

輸出結果為:

7

5.set_by_lua_file指令

set_by_lua_file可以呼叫本地Lua指令碼檔案。語法與set_by_lua相同:

set_by_lua_file $res <lua-script-str> [$arg1 $arg2 ...]

在conf資料夾下建立hello.lua檔案:

local a = tonumber(ngx.arg[1])
local b = tonumber(ngx.arg[2])
return a+b

在conf資料夾下建立nginx_lua.conf檔案:

worker_processes  1;
error_log logs/error.log;
events {
worker_connections 1024;
}
http {
server {
listen 9000;
location = / {
default_type text/html;
set_by_lua_file $res "conf/hello.lua" $arg_a $arg_b;
echo $res;
}
}
}

啟動nginx程序:

/usr/local/openresty/nginx/sbin/nginx -p /home/www/ -c conf/nginx_lua.conf

接下來我們可以使用 curl 來測試是否能夠正常範圍:

curl 'localhost:9000/?a=2&b=5'

輸出結果為:

7

6.設定nginx變數

在conf資料夾下建立nginx.conf檔案:

worker_processes  1;
error_log logs/error.log;
events {
worker_connections 1024;
}
http {
server {
listen 9000;
location / {
set $a $host;
set $b 'hello world';
default_type text/html;
content_by_lua_file 'conf/hello.lua';
}
}
}

在conf資料夾下建立hello.lua:

local var = ngx.var
ngx.say("ngx.var.a : ", var.a, "<br/>")
ngx.say("ngx.var.b : ", var.b, "<br/>")
ngx.say("<br/>")

啟動nginx程序:

/usr/local/openresty/nginx/sbin/nginx -p /home/www/ -c conf/nginx_lua.conf

接下來我們可以使用 curl 來測試是否能夠正常範圍:

curl 'localhost:9000'

輸出結果為:

ngx.var.a : localhost<br/>
ngx.var.b : hello world<br/>

【本文版權歸微信公眾號"程式碼藝術"(ID:onblog)所有,若是轉載請務必保留本段原創宣告,違者必究。若是文章有不足之處,歡迎關注微信公眾號私信與我進行交流!】

7.執行週期

現在已經學會了content_by_lua 與 set_by_lua 指令,其它類似的指令還有很多,那麼這些指令都是有什麼區別呢?主要區別是指令的執行週期不同,如圖所示。(圖片來源於網路)

8.其它指令

指令 所處處理階段 使用範圍 解釋
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階段處理,比如記錄訪問量/統計平均響應時間

9.Nginx API

將下面的lua指令碼複製到你的content_by_lua_file指定的lua檔案中即可。

--請求頭
local headers = ngx.req.get_headers()
ngx.say("headers begin", "<br/>")
ngx.say("Host : ", headers["Host"], "<br/>")
ngx.say("user-agent : ", headers["user-agent"], "<br/>")
ngx.say("user-agent : ", headers.user_agent, "<br/>")
for k, v in pairs(headers) do
if type(v) == "table" then
ngx.say(k, " : ", table.concat(v, ","), "<br/>")
else
ngx.say(k, " : ", v, "<br/>")
end
end
ngx.say("headers end", "<br/>")
ngx.say("<br/>") --get請求uri引數
ngx.say("uri args begin", "<br/>")
local uri_args = ngx.req.get_uri_args()
for k, v in pairs(uri_args) do
if type(v) == "table" then
ngx.say(k, " : ", table.concat(v, ", "), "<br/>")
else
ngx.say(k, ": ", v, "<br/>")
end
end
ngx.say("uri args end", "<br/>")
ngx.say("<br/>") --post請求引數
ngx.req.read_body()
ngx.say("post args begin", "<br/>")
local post_args = ngx.req.get_post_args()
for k, v in pairs(post_args) do
if type(v) == "table" then
ngx.say(k, " : ", table.concat(v, ", "), "<br/>")
else
ngx.say(k, ": ", v, "<br/>")
end
end
ngx.say("post args end", "<br/>")
ngx.say("<br/>") --請求的http協議版本
ngx.say("ngx.req.http_version : ", ngx.req.http_version(), "<br/>")
--請求方法
ngx.say("ngx.req.get_method : ", ngx.req.get_method(), "<br/>")
--原始的請求頭內容
ngx.say("ngx.req.raw_header : ", ngx.req.raw_header(), "<br/>")
--請求的body內容體
ngx.say("ngx.req.get_body_data() : ", ngx.req.get_body_data(), "<br/>")
ngx.say("<br/>") local request_uri = ngx.var.request_uri;
ngx.say("request_uri : ", request_uri, "<br/>");
--解碼
ngx.say("decode request_uri : ", ngx.unescape_uri(request_uri), "<br/>");
--MD5
ngx.say("ngx.md5 : ", ngx.md5("123"), "<br/>")
--http time
ngx.say("ngx.http_time : ", ngx.http_time(ngx.time()), "<br/>") --當前時間
ngx.update_time()
local now = ngx.now()
ngx.say("nowTime : ", now, "<br/>")

訪問 http://127.0.0.1:9000/?a=8&b=55 ,手動輸入兩個Cookie,輸出結果:

headers begin
Host : 127.0.0.1:9000
user-agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
user-agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
accept-language : zh-CN,zh;q=0.9
connection : keep-alive
accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
cache-control : max-age=0
host : 127.0.0.1:9000
cookie : hello=world; Heelo=Sdd
accept-encoding : gzip, deflate
upgrade-insecure-requests : 1
user-agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
headers end uri args begin
b: 55
a: 8
uri args end post args begin
post args end ngx.req.http_version : 1.1
ngx.req.get_method : GET
ngx.req.raw_header : GET /?a=8&b=55 HTTP/1.1 Host: 127.0.0.1:9000 Connection: keep-alive Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: hello=world; Heelo=Sdd
ngx.req.get_body_data() : nil request_uri : /?a=8&b=55
decode request_uri : /?a=8&b=55
ngx.md5 : 202cb962ac59075b964b07152d234b70
ngx.http_time : Mon, 29 Apr 2019 14:53:17 GMT

Lua如何呼叫系統shell呢?

--呼叫系統命令
local t = io.popen('cat /home/www/conf/hello.lua')
local a = t:read("*all")
t:close()
ngx.say(a)

10.Lua發起Http請求

Lua傳送Http請求,預設是不支援的,需要引入第三方庫。也就是下方這個Github地址的兩個檔案:http.luahttp_headers.lua

Github:https://github.com/ledgetech/lua-resty-http/tree/master/lib/resty

流程和上方的Demo類似,不同的是,需要在你的 lua 檔案中寫入一行程式碼:

local http = require "resty.http"

啟動你的 nginx ,命令同上。使用 curl 命令測試訪問,檢視 logs 目錄下的 error.log 檔案:

2019/04/30 14:23:21 [error] 7093#0: *1 lua entry thread aborted: runtime error: /home/www/conf/helloworld.lua:95: module 'resty.http' not found:
no field package.preload['resty.http']
no file '/usr/local/openresty/lualib/resty/http.lua'
no file '/usr/local/openresty/lualib/resty/http/init.lua'
no file './resty/http.lua'
no file '/usr/local/openresty/luajit/share/luajit-2.1.0-beta1/resty/http.lua'
no file '/usr/local/share/lua/5.1/resty/http.lua'
no file '/usr/local/share/lua/5.1/resty/http/init.lua'
no file '/usr/local/openresty/luajit/share/lua/5.1/resty/http.lua'
no file '/usr/local/openresty/luajit/share/lua/5.1/resty/http/init.lua'
no file '/usr/local/openresty/lualib/resty/http.so'
no file './resty/http.so'
no file '/usr/local/lib/lua/5.1/resty/http.so'
no file '/usr/local/openresty/luajit/lib/lua/5.1/resty/http.so'
no file '/usr/local/lib/lua/5.1/loadall.so'
no file '/usr/local/openresty/lualib/resty.so'
no file './resty.so'
no file '/usr/local/lib/lua/5.1/resty.so'
no file '/usr/local/openresty/luajit/lib/lua/5.1/resty.so'
no file '/usr/local/lib/lua/5.1/loadall.so'

我們只需要在錯誤日誌的第一個目錄下新增上面兩個http模組的lua檔案即可。目錄:

/usr/local/openresty/lualib/resty/

接下來就是重啟你的nginx,再次訪問,發現不報錯了。

GET請求

local http = require "resty.http"
local function http_post_client(url, timeout)
local httpc = http.new() timeout = timeout or 30000
httpc:set_timeout(timeout) local res, err_ = httpc:request_uri(url, {
method = "GET",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
}
})
httpc:set_keepalive(5000, 100)
--httpc:close()
return res, err_
end

POST請求

local http = require "resty.http"
local function http_post_client(url,body,timeout)
local httpc = http.new() timeout = timeout or 30000
httpc:set_timeout(timeout) local res, err_ = httpc:request_uri(url, {
method = "POST",
body = body,
headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
}
})
httpc:set_keepalive(5000, 100)
httpc:close()
if not res then
return nil, err_
else if res.status == 200 then
return res.body, err_
else
return nil, err_ end
end end

Demo

--get
local resp, err = http_post_client("http://127.0.0.1/index.html?name=test",3000)
--post
local body = {"name" = "test"}
local resp, err = http_post_client("http://127.0.0.1/index.html?name=test",body,3000)

參考

https://blog.csdn.net/qq_21860077/article/details/83623888

Nginx API for Lua:https://www.cnblogs.com/wangxusummer/p/4309007.html

版權宣告

【本文版權歸微信公眾號"程式碼藝術"(ID:onblog)所有,若是轉載請務必保留本段原創宣告,違者必究。若是文章有不足之處,歡迎關注微信公眾號私信與我進行交流!】