## 概述
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`文件代碼
```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 in pairs(headers) do
if type(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 in pairs(uri_args) do
if type(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 in pairs(post_args) do
if type(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
[1] 1264
HTTP/1.1 200 OK
Server: openresty/1.17.8.2
Date: Tue, 16 Jul 2024 00: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.1 302 Moved Temporarily
Server: openresty/1.17.8.2
Date: Tue, 16 Jul 2024 01: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.1 200 OK
Server: openresty/1.17.8.2
Date: Tue, 16 Jul 2024 01: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 2024 01:38:43 GMT
```
如果訪問出現`500 Internal Server Error` 請通過nginx錯誤日志排查,下面錯誤表示缺少一個結束符`;`
```
[error] 7#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`腳本代碼
```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.1 200 OK
Server: openresty/1.17.8.2
Date: Tue, 16 Jul 2024 01: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](http://wiki.nginx.org/HttpLuaModule#ngx.shared.DICT)。
- 設計模式系列
- 工廠方法模式
- 序言
- Windows程序注冊為服務的工具WinSW
- 基礎
- 安裝
- 開發規范
- 目錄結構
- 配置
- 快速入門
- 架構
- 請求流程
- 架構總覽
- URL訪問
- 容器和依賴注入
- 中間件
- 事件
- 代碼層結構
- 四個層次
- 路由
- 控制器
- 請求
- 響應
- 數據庫
- MySQL實時同步數據到ES解決方案
- 阿里云DTS數據MySQL同步至Elasticsearch實戰
- PHP中的MySQL連接池
- PHP異步非阻塞MySQL客戶端連接池
- 模型
- 視圖
- 注解
- @SpringBootApplication(exclude={DataSourceAutoConfiguration.calss})
- @EnableFeignClients(basePackages = "com.wotu.feign")
- @EnableAspectJAutoProxy
- @EnableDiscoveryClient
- 錯誤和日志
- 異常處理
- 日志處理
- 調試
- 驗證
- 驗證器
- 驗證規則
- 擴展庫
- 附錄
- Spring框架知識體系詳解
- Maven
- Maven和Composer
- 構建Maven項目
- 實操課程
- 01.初識SpringBoot
- 第1章 Java Web發展史與學習Java的方法
- 第2章 環境與常見問題踩坑
- 第3章 springboot的路由與控制器
- 02.Java編程思想深度理論知識
- 第1章 Java編程思想總體
- 第2章 英雄聯盟的小案例理解Java中最為抽象的概念
- 第3章 徹底理解IOC、DI與DIP
- 03.Spring與SpringBoot理論篇
- 第1章 Spring與SpringBoot導學
- 第2章 Spring IOC的核心機制:實例化與注入
- 第3章 SpringBoot基本配置原理
- 04.SprinBoot的條件注解與配置
- 第1章 conditonal 條件注解
- 第2章 SpringBoot自動裝配解析
- 05.Java異常深度剖析
- 第1章 Java異常分類剖析與自定義異常
- 第2章 自動配置Url前綴
- 06.參數校驗機制與LomBok工具集的使用
- 第1章 LomBok工具集的使用
- 第2章 參數校驗機制以及自定義校驗
- 07.項目分層設計與JPA技術
- 第1章 項目分層原則與層與層的松耦合原則
- 第2章 數據庫設計、實體關系與查詢方案探討
- 第3章 JPA的關聯關系與規則查詢
- 08.ORM的概念與思維
- 第1章 ORM的概念與思維
- 第2章 Banner等相關業務
- 第3章 再談數據庫設計技巧與VO層對象的技巧
- 09.JPA的多種查詢規則
- 第1章 DozerBeanMapper的使用
- 第2章 詳解SKU的規格設計
- 第3章 通用泛型Converter
- 10.令牌與權限
- 第1章 通用泛型類與java泛型的思考
- 常見問題
- 微服務
- demo
- PHP中Self、Static和parent的區別
- Swoole-Cli
- 為什么要使用現代化PHP框架?
- 公眾號
- 一鍵部署微信公眾號Markdown編輯器(支持適配和主題設計)
- Autodesigner 2.0發布
- Luya 一個現代化PHP開發框架
- PHPZip - 創建、讀取和管理 ZIP 文件的簡單庫
- 吊打Golang的PHP界天花板webman壓測對比
- 簡潔而強大的 YAML 解析庫
- 推薦一個革命性的PHP測試框架:Kahlan
- ServBay下一代Web開發環境
- 基于Websocket和Canvas實現多人協作實時共享白板
- Apipost預執行腳本如何調用外部PHP語言
- 認證和授權的安全令牌 Bearer Token
- Laradock PHP 的 Docker 完整本地開發環境
- 高效接口防抖策略,確保數據安全,避免重復提交的終極解決方案!
- TIOBE 6月榜單:PHP穩步前行,編程語言生態的微妙變化
- Aho-Corasick字符串匹配算法的實現
- Redis鍵空間通知 Keyspace Notification 事件訂閱
- ServBay如何啟用并運行Webman項目
- 使用mpdf實現導出pdf文件功能
- Medoo 輕量級PHP數據庫框架
- 在PHP中編寫和運行單元測試
- 9 PHP運行時基準性能測試
- QR碼生成器在PHP中的源代碼
- 使用Gogs極易搭建的自助Git服務
- Gitea
- webman如何記錄SQL到日志?
- Sentry PHP: 實時監測并處理PHP應用程序中的錯誤
- Swoole v6 Alpha 版本已發布
- Proxypin
- Rust實現的Redis內存數據庫發布
- PHP 8.4.0 Alpha 1 測試版本發布
- 121
- Golang + Vue 開發的開源輕量 Linux 服務器運維管理面板
- 內網穿透 FRP VS Tailscale
- 新一代開源代碼托管平臺Gitea
- 微服務系列
- Nacos云原生配置中心介紹與使用
- 輕量級的開源高性能事件庫libevent
- 國密算法
- 國密算法(商用密碼)
- GmSSL 支持國密SM2/SM3/SM4/SM9/SSL 密碼工具箱
- GmSSL PHP 使用
- 數據庫
- SQLite數據庫的Web管理工具
- 阿里巴巴MySQL數據庫強制規范
- PHP
- PHP安全測試秘密武器 PHPGGC
- 使用declare(strict_types=1)來獲得更健壯的PHP代碼
- PHP中的魔術常量
- OSS 直傳阿里騰訊示例
- PHP源碼編譯安裝APCu擴展實現數據緩存
- BI性能DuckDB數據管理系統
- 為什么別人可以是架構師!而我卻不是?
- 密碼還在用 MD5 加鹽?不如試試 password_hash
- Elasticsearch 在電商領域的應用與實踐
- Cron 定時任務入門
- 如何動態設置定時任務!而不是寫死在Linux Crontab
- Elasticsearch的四種查詢方式,你知道多少?
- Meilisearch vs Elasticsearch
- OpenSearch vs Elasticsearch
- Emlog 輕量級開源博客及建站系統
- 現代化PHP原生協程引擎 PRipple
- 使用Zephir編寫C擴展將PHP源代碼編譯加密
- 如何將PHP源代碼編譯加密,同時保證代碼能正常的運行
- 為什么選擇Zephir給PHP編寫動態擴展庫?
- 使用 PHP + XlsWriter實現百萬級數據導入導出
- Rust編寫PHP擴展
- 阿里云盤開放平臺對接進行文件同步
- 如何構建自己的PHP靜態可執行文件
- IM后端架構
- RESTful設計方法和規范
- PHP編譯器BPC 7.3 發布,成功編譯ThinkPHP8
- 高性能的配置管理擴展 Yaconf
- PHP實現雪花算法庫 Snowflake
- PHP官方現代化核心加密庫Sodium
- pie
- 現代化、精簡、非阻塞PHP標準庫PSL
- PHP泛型和集合
- 手把手教你正確使用 Composer包管理
- JWT雙令牌認證實現無感Token自動續期
- 最先進PHP大模型深度學習庫TransformersPHP
- PHP如何啟用 FFI 擴展
- PHP超集語言PXP
- 低延遲雙向實時事件通信 Socket.IO
- PHP OOP中的繼承和多態
- 強大的現代PHP高級調試工具Kint
- PHP基金會
- 基于webman+vue3高質量中后臺框架SaiAdmin
- 開源免費的定時任務管理系統:Gocron
- 簡單強大OCR工具EasyOCR在PHP中使用
- PHP代碼抽象語法樹工具PHP AST Viewer
- MySQL數據庫管理工具PHPMyAdmin
- Rust編寫的一款高性能多人代碼編輯器Zed
- 超高性能PHP框架Workerman v5.0.0-beta.8 發布
- 高并發系列
- 入門介紹及安裝
- Lua腳本開發 Hello World
- 執行流程與階段詳解
- Nginx Lua API 接口開發
- Lua模塊開發
- OpenResty 高性能的正式原因
- 記一次查找 lua-resty-mysql 庫 insert_id 的 bug
- 包管理工具OPM和LuaRocks使用
- 異步非阻塞HTTP客戶端庫 lua-resty-http
- Nginx 內置綁定變量
- Redis協程網絡庫 lua-resty-redis
- 動態HTML渲染庫 lua-testy-template
- 單獨的
- StackBlitz在線開發環境
- AI
- 基礎概念
- 12312
- 基礎鏡像的坑
- 利用phpy實現 PHP 編寫 Vision Transformer (ViT) 模型
- 語義化版本 2.0.0