## 概述
Lua-resty-http 是一個基于 OpenResty 的 Lua 庫,是 OpenResty 項目中一個非常有用的模塊,用于從 Nginx 服務中發起 HTTP 請求。OpenResty 是一個基于 Nginx 與 LuaJIT 的全功能 Web 平臺,它集成了大量精心設計的 Nginx 模塊,以及大量的 Lua 庫。
lua-resty-http 庫允許你在 OpenResty 的 Lua 環境中輕松地發送 HTTP 請求,它提供了一個簡單易用的 API 來處理 HTTP 請求和響應。這使得在 Nginx 配置文件中編寫 Lua 腳本來處理 HTTP 請求和響應成為可能,從而可以構建高性能的 Web 應用和服務。如果你正在使用 OpenResty 并需要在 Nginx 配置中發起 HTTP 請求,lua-resty-http 是一個非常合適的選擇。
>項目地址:https://github.com/ledgetech/lua-resty-http
## 特性
* 異步非阻塞:該庫利用 nginx 的事件循環模型,讓 HTTP 請求在后臺執行,不會阻塞主線程,提高了整體性能。
* 連接池管理:它支持連接池的創建與管理,可以有效地復用 TCP 連接,減少握手延遲,提高服務響應速度。
* 豐富的 API 設計:Lua-Resty-HTTP 提供了一套完整的 API,包括設置超時、指定代理、添加請求頭、處理重定向、自定義認證等,使得開發過程更為便捷。
* SSL/TLS 支持:內置 SSL/TLS 功能,支持 HTTPS 請求,且允許自定義證書和密鑰。
* 錯誤處理:提供了詳細的錯誤信息,便于調試和故障排除。
## 應用場景
* 數據獲取:從 RESTful API 獲取 JSON 或其他格式的數據。
* API 調用:在你的 OpenResty 應用中調用外部 Web 服務。
* 自動化測試:在 Lua 測試腳本中模擬 HTTP 請求,驗證服務行為。
* 日志報告:向遠程服務器發送日志或統計信息。
* 緩存刷新:根據 HTTP 響應自動更新本地緩存。
## 使用
#### 安裝
這里通過OPM工具包安裝,更多請查看 [ddd](ddd)
```
opm get ledgetech/lua-resty-http
```
#### 基礎使用
使用 Lua-resty-http 發送 HTTP 請求的一個基本示例
```
local httpc = require("resty.http").new()
-- Single-shot requests use the `request_uri` interface.
local res, err = httpc:request_uri("http://example.com/helloworld", {
method = "POST",
body = "a=1&b=2",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
},
})
if not res then
ngx.log(ngx.ERR, "request failed: ", err)
return
end
-- At this point, the entire request / response is complete and the connection
-- will be closed or back on the connection pool.
-- The `res` table contains the expeected `status`, `headers` and `body` fields.
local status = res.status
local length = res.headers["Content-Length"]
local body = res.body
```
### 進階使用
`openresty.tinywan.com.conf`配置文件
```
server {
listen 80;
server_name openresty.tinywan.com;
location /lua_http_test {
default_type "text/html";
lua_code_cache off;
content_by_lua_file conf/lua/lua_http_test.lua;
}
}
```
`lua_http_test.lua` 腳本
```
local httpc = require("resty.http").new()
-- Single-shot requests use the `request_uri` interface.
local res, err = httpc:request_uri("https://www.workerman.net/u/Tinywan", {
method = "GET",
body = "name=Tinywan&age=24",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
},
ssl_verify = false,
})
if not res then
ngx.log(ngx.ERR, "request failed: ", err)
return
end
local status = res.status
local length = res.headers["Content-Length"]
local body = res.body
ngx.say(res.body)
```
通過curl腳本測試請求打印結果
```html
$ curl -i http://openresty.tinywan.com/lua_http_test
HTTP/1.1 200 OK
Server: openresty/1.17.8.2
Date: Wed, 17 Jul 2024 09:42:10 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
<!doctype html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="shortcut icon" href="/favicon.ico" />
<link href="https://cdn.workerman.net/css/bootstrap.min.css?v=20211126" rel="stylesheet">
<link href="https://cdn.workerman.net/css/main.css?v=20240705" rel="stylesheet">
<script src="https://cdn.workerman.net/js/jquery.min.js"></script>
<script src="https://cdn.workerman.net/js/bootstrap.min.js?v=20211126"></script>
<script src="https://cdn.workerman.net/js/functions.js?v=20220507"></script>
<script type="text/javascript" charset="UTF-8" src="https://cdn.wwads.cn/js/makemoney.js" async></script>
<title>Tinywan的主頁-分享-workerman社區</title>
</head>
...
</body>
</html>
```
#### 項目應用
```
--[[--------------------------------------------------------
* | Copyright (C) Shaobo Wan (Tinywan)
* | Origin: 開源技術小棧
* |--------------------------------------------------------
--]]
local helper = require "vendor.helper"
local redis = require "resty.redis"
local resty_lock = require "resty.lock"
local http = require "resty.http"
local log = ngx.log
local ERR = ngx.ERR
local live_ngx_cache = ngx.shared.live_ngx_cache
-- 非error 日志開關 1:開啟,0:關閉
local log_switch = 1
local redis_host = "127.0.0.1"
local redis_port = 6379
local redis_auth = "123456"
local redis_timeout = 1000
-- set ngx.cache
local function set_cache(key, value, exptime)
if not exptime then
exptime = 0
end
local succ, err, forcible = live_ngx_cache:set(key, value, exptime)
return succ
end
-- close redis
local function close_redis(red)
if not red then
return
end
--釋放連接(連接池實現)
local pool_max_idle_time = 10000 --毫秒
local pool_size = 100 --連接池大小
local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
if not ok then
log(ERR, "set redis keepalive error : ", err)
end
end
-- read redis
local function read_redis(_host, _port, _auth, keys)
local red = redis:new()
red:set_timeout(redis_timeout)
local ok, err = red:connect(_host, _port)
if not ok then
log(ERR, "connect to redis error : ", err)
end
-- 請注意這里 auth 的調用過程
local count
count, err = red:get_reused_times()
if 0 == count then
ok, err = red:auth(_auth)
if not ok then
log(ERR, "failed to auth: ", err)
return close_redis(red)
end
elseif err then
log(ERR, "failed to get reused times: ", err)
return close_redis(red)
end
local resp = nil
if #keys == 1 then
resp, err = red:get(keys[1])
else
resp, err = red:mget(keys)
end
if not resp then
log(ERR, keys[1] .. " get redis content error : ", err)
return close_redis(red)
end
if resp == ngx.null then
resp = nil
end
close_redis(red)
if log_switch == 1 then
log(ERR, "[2] [read_redis] content from redis.cache id = " .. keys[1]) -- tag data origin
end
return resp
end
-- write redis
local function write_redis(_host, _port, _auth, keys, values)
local red = redis:new()
red:set_timeout(redis_timeout)
local ok, err = red:connect(_host, _port)
if not ok then
log(ERR, "connect to redis error : ", err)
end
local count
count, err = red:get_reused_times()
if 0 == count then
ok, err = red:auth(_auth)
if not ok then
log(ERR, "failed to auth: ", err)
return close_redis(red)
end
elseif err then
log(ERR, "failed to get reused times: ", err)
return close_redis(red)
end
-- set data
local resp = nil
if #keys == 1 then
resp, err = red:set(keys[1], values)
else
resp, err = red:mset(keys, values)
end
if not resp then
log(ERR, "set redis live error : ", err)
close_redis(red)
end
close_redis(red)
return resp
end
-- get ngx.cache
--[1]即使發生其他一些不相關的錯誤,您也需要盡快解除鎖定。
--[2]在釋放鎖之前,您需要從后端獲得的結果更新緩存,以便其他已經等待鎖定的線程在獲得鎖定后才能獲得緩存值。
--[3]當后端根本沒有返回任何值時,我們應該通過將一些存根值插入緩存來仔細處理。
local function read_cache(key)
local ngx_resp = nil
-- 獲取共享內存上key對應的值。如果key不存在,或者key已經過期,將會返回nil;如果出現錯誤,那么將會返回nil以及錯誤信息。
-- step 1
local val, err = live_ngx_cache:get(key)
if val then
if log_switch == 1 then
log(ERR, " [1] [read_ngx_cache] content from ngx.cache id = " .. key) -- tag data origin
end
return val
end
if err then
log(ERR, "failed to get key from shm: ", err)
end
-- cache miss!
-- step 2:
local lock, err = resty_lock:new("cache_lock") -- new resty.lock
if not lock then
log(ERR, "failed to create lock [cache_lock] : ", err)
return
end
local elapsed, err = lock:lock(key) -- 鎖
if not elapsed then
log(ERR, "failed to acquire the lock", err)
return
end
-- lock successfully acquired!
-- step 3:
-- someone might have already put the value into the cache ,so we check it here again:
val, err = live_ngx_cache:get(key)
if val then
local ok, err = lock:unlock()
if not ok then
log(ERR, "failed to unlock [111] : ", err)
end
return val
end
--- step 4:
local val = read_redis(redis_host, redis_port, redis_auth, { key })
if not val then
local ok, err = lock:unlock()
if not ok then
log(ERR, "failed to unlock [222] : ", err)
end
-- FIXME: we should handle the backend miss more carefully
-- here, like inserting a stub value into the cache.
log(ERR, "[4] ngx.cache find redis cache no value found : ", err)
return ngx_resp
end
-- [lock] update the shm cache with the newly fetched value
local ok, err = live_ngx_cache:set(key, val, 1)
if not ok then
local ok, err = lock:unlock()
if not ok then
log(ERR, "failed to unlock [333] : ", err)
end
log(ERR, "failed to update live_ngx_cache: ", err)
end
local ok, err = lock:unlock()
if not ok then
log(ERR, "failed to unlock [444] : ", err)
end
return val
end
-------------- read_http 大并發采用 resty.http ,對于:ngx.location.capture 慎用
local function read_http(id)
local httpc = http.new()
local resp, err = httpc:request_uri("https://live.tinywan.com", {
method = "GET",
path = "/api/live/" .. id,
headers = {
["User-Agent"] = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36"
}
})
if not resp then
log(ERR, "resty.http API request error :", err)
return
end
httpc:close()
-- 判斷狀態碼
if resp.status ~= ngx.HTTP_OK then
log(ERR, "request error, status :", resp.status)
return
end
if resp.status == ngx.HTTP_FORBIDDEN then
log(ERR, "request error, status :", resp.status)
return
end
-- backend not data 判斷api返回的狀態碼
local status_code = helper.cjson_decode(resp.body)['code']
if tonumber(status_code) == 200 then
-- 正常數據緩存到 Redis 數據緩存
local live_info_key = "LIVE_TABLE:" .. id
local live_value = helper.cjson_decode(resp.body)['data'] -- 解析的Lua自己的然后存儲到Redis 數據庫中去(這里最好使用lua的json格式去寫入)
local live_live_str = write_redis(redis_host, redis_port, redis_auth, { live_info_key }, helper.cjson_encode(live_value))
if not live_live_str then
log(ERR, "redis set info error: ")
end
if log_switch == 1 then
log(ERR, "[3] [read_http] content from backend API id : " .. id) -- tag data origin
end
return helper.cjson_encode(live_value)
else
-- 后端沒有數據直接返回 nil
log(ERR, " [read_http] backend API is not content return error_msg : " .. helper.cjson_decode(resp.body)['msg']) -- tag data origin
return
end
end
-- 業務邏輯處理
local function read_content(id)
local cache_content = nil
local live_key = "LIVE:" .. id
local content = read_cache(live_key)
if not content then
log(ERR, "[5] redis not found content, back to backend API , id : ", id)
content = read_http(id)
end
if not content then
log(ERR, "backend API not found content, id : ", id)
return cache_content
end
if tostring(content) == "false" then
log(ERR, "backend API content is false ", id)
return cache_content
end
return content
end
local _M = {
read_content = read_content
}
return _M
```
渲染請求效果

- 設計模式系列
- 工廠方法模式
- 序言
- 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