lua web快速開發指南(7)
httpc庫基於cf框架都內部實現的socket編寫的http client庫.
httpc庫內建SSL支援, 在不使用代理的情況下就可以請求第三方介面.
httpc支援header、args、body、timeout請求設定, 完美支援各種httpc呼叫方式.
API介紹
httpc庫使用前需要手動匯入httpc庫: local httpc = require "httpc"
.
httpc.get(domain, HEADER, ARGS, TIMEOUT)
呼叫get方法將會對domain發起一次HTTP GET請求.
domain是一個符合URL定義規範的字串;
HEADER是一個key-value陣列, 一般用於新增自定義頭部;
ARGS為請求引數的key-value陣列, 對於GET方法將會自動格式化為:args[n][1]=args[n][2]&args[n+1][1]=args[n+1][2]
;
TIMEOUT為httpc請求的最大超時時間;
httpc.post(domain, HEADER, BODY, TIMEOUT)
呼叫post方法將會對domain發起一次HTTP POST請求, 此方法的content-type
會被設定為:application/x-www-form-urlencoded
.
domain是一個符合URL定義規範的字串;
HEADER是一個key-value陣列, 一般用於新增自定義頭部; 不支援Content-Type與Content-Length設定;
BODY是一個key-value陣列, 對於POST方法將會自動格式化為:body[n][1]=body[n][2]&body[n+1][1]=body[n+1][2]
;
TIMEOUT為httpc請求的最大超時時間;
httpc.json(domain, HEADER, JSON, TIMEOUT)
json方法將會對domain發起一次http POST請求. 此方法的content-type
會被設定為:application/json
.
HEADER是一個key-value陣列, 一般用於新增自定義頭部; 不支援Content-Type與Content-Length設定;
JSON必須是一個字串型別;
TIMEOUT為httpc請求的最大超時時間;
httpc.file(domain, HEADER, FILES, TIMEOUT)
file方法將會對domain發起一次http POST請求.
HEADER是一個key-value陣列, 一般用於新增自定義頭部; 不支援Content-Type與Content-Length設定;
FILES是一個key-value陣列, 每個item包含: name(名稱), filename(檔名), file(檔案內容), type(檔案型別)等屬性. 檔案型別可選.
TIMEOUT為httpc請求的最大超時時間;
httpc 返回值
所有httpc請求介面均會有2個返回值: code
, response
. code為http協議狀態碼, response為迴應body(字串型別).
引數不正確, 連線被斷開等其它錯誤, code將會為nil, response為錯誤資訊.
"一次性HTTP請求"
什麼是一次性httpc請求呢?
每次我們使用httpc庫在請求第三方http介面的時候, 都會在介面返回後關閉連線. 這在日常使用中一邊也沒什麼問題.
但是當我們需要多次請求同一個介面的時候, 每次請求完畢就關閉連線顯然不是那麼高效, 現在我們嘗試使用一個http class物件來解決這個問題.
注意: httpc class物件不能對不同域名的介面使用同一個連線, 這會返回一個錯誤呼叫給使用者.
httpc庫的class物件使用介紹
要使用httpc的class
需要匯入httpc的class庫, 匯入方式為: local httpc = require "httpc.class"
.
當需要使用httpc發起請求之前, 需要先建立一個httpc的物件, 如: local hc = httpc:new {}
. httpc物件建立與初始化完畢後, 使用方式同上述API所示.
hc
與httpc
擁有相同的API, 但是需要使用不同的呼叫方式. 如: hc:get
、hc:post
、hc:json
、hc:file
.
一旦hc
使用完畢時, 需要顯示的呼叫hc:close()
方法來關閉建立的httpc物件並銷燬httpc的連線.
開始實踐
現在, 讓我們將上面學到的API使用方式運用到實踐中.
1. 啟動一個httpd庫的web server
在main.lua
中啟動一個httpd
的server.
local httpd = require "httpd"
local json = require "json"
local app = httpd:new("httpd")
app:listen("", 8080)
app:run()
1. 增加一個API路由用於ip地址歸屬地查詢
我們先利用httpd
庫啟動一個server服務, 並且對外提供IP歸屬地查詢介面
app:api('/ip', function(content)
local httpc = require "httpc"
local args = content.args
if not args or not args['ip'] then
return json.encode({
code = 400,
msg = "錯誤的介面呼叫方式",
data = json.null,
})
end
local code, response = httpc.get("http://freeapi.ipip.net/"..args["ip"])
if code ~= 200 then
return json.encode({
code = 401,
msg = "獲取資料失敗",
data = json.null,
})
end
return response
end)
現在程式碼已經完成! 讓我們開啟瀏覽器輸入:http://localhost:8090/ip?ip=8.8.8.8
檢視返回資料.
2. 查詢多個IP地址的歸屬地
一個請求對應一次回是HTTP協議的本質! 但是我們經常會遇到批量請求的業務場景, 我們就以此來設計一個批量請求/返回的例子.
讓我們假設客戶端將會發送一次POST請求, body為json型別並且裡面包含一個IP陣列: ip_list = {1.1.1.1, 8.8.8.8, 114.114.114.114}.
服務端在接受到這個陣列之後, 需要將這ip的歸屬地資訊一次性返回給客戶端.
app:api('/ips', function(content)
local httpc = require "httpc.class"
if not content.json then
return json.encode({
code = 400,
msg = "錯誤的呼叫引數",
data = json.null,
})
end
local args = json.decode(content.body)
if type(args) ~= 'table' or type(args['ip_list']) ~= 'table' then
return json.encode({
code = 400,
msg = "錯誤的引數型別",
data = json.null,
})
end
local hc = httpc:new {}
local ret = { code = 200 , data = {}}
for _, ip in ipairs(args['ip_list']) do
local code, response = hc:get("http://freeapi.ipip.net/"..ip)
ret['data'][#ret['data']+1] = json.decode(response)
end
return json.encode(ret)
end)
由於普通瀏覽器POST無法傳送json, 讓我們使用curl
命令列工具進行測試:
curl -H "Content-Type: application/json" -X POST -d '{"ip_list":["1.1.1.1","8.8.8.8","114.114.114.114"]}' http://localhost:8090/ip
返回資料如下:
{"code":200,"data":[["CLOUDFLARE.COM","CLOUDFLARE.COM","","",""],["GOOGLE.COM","GOOGLE.COM","","","level3.com"],["114DNS.COM","114DNS.COM","","",""]]}
3. 持續優化.
上述例子似乎已經非常完美! 我們利用連線保持的方式進行了3次請求, 這樣已經縮短了請求50%的連線消耗(TCP握手).
但是對於非常需要效能的我們來說: 每次請求需要等到上一個請求處理完畢後才能繼續發起新的請求, 這樣的方式顯然還不足以滿足我們.
這樣的情況下, httpc
庫提供了一個叫multi_request
的方法. 具體使用方法在這裡.
這個方法可以讓我們同時傳送幾十上百個請求來解決單個連線阻塞的問題.
4. 併發請求
現在, 讓我使用httpc
庫的multi_request
方法來併發請求多個介面, 減少連線阻塞帶來的問題.
app:api('/ips_multi', function (content)
local httpc = require "httpc"
if not content.json then
return json.encode({
code = 400,
msg = "錯誤的呼叫引數",
data = json.null,
})
end
local args = json.decode(content.body)
if type(args) ~= 'table' or type(args['ip_list']) ~= 'table' then
return json.encode({
code = 400,
msg = "錯誤的引數型別",
data = json.null,
})
end
local requests = {}
local responses = { code = 200, data = {}}
for _, ip in ipairs(args["ip_list"]) do
requests[#requests+1] = {
domain = "http://freeapi.ipip.net/"..ip,
method = "get",
}
end
local ok, ret = httpc.multi_request(requests)
for _, res in ipairs(ret) do
responses['data'][#responses['data'] + 1] = res
end
return json.encode(responses)
end)
好的, 現在讓我們再次使用curl
工具進行測試:
curl -H "Content-Type: application/json" -X POST -d '{"ip_list":["1.1.1.1","8.8.8.8","114.114.114.114"]}' http://localhost:8090/ips_multi
我們可以從cf的請求迴應時間看到, 響應時間消耗再次降低了50%.
[candy@MacBookPro:~/Documents/core_framework] $ ./cfadmin
[2019/06/16 17:45:21] [INFO] httpd正在監聽: 0.0.0.0:8090
[2019/06/16 17:45:21] [INFO] httpd正在執行Web Server服務...
[2019/06/16 17:45:23] - ::1 - ::1 - /ips_multi - POST - 200 - req_time: 0.140253/Sec
[2019/06/16 17:45:38] - ::1 - ::1 - /ips - POST - 200 - req_time: 0.288286/Sec
完整的程式碼
local httpd = require "httpd"
local json = require "json"
local app = httpd:new("httpd")
app:api('/ip', function(content)
local httpc = require "httpc"
local args = content.args
if not args or not args['ip'] then
return json.encode({
code = 400,
msg = "錯誤的介面呼叫方式",
data = json.null,
})
end
local code, response = httpc.get("http://freeapi.ipip.net/"..args["ip"])
if code ~= 200 then
return json.encode({
code = 401,
msg = "獲取資料失敗",
data = json.null,
})
end
return response
end)
app:api('/ips', function(content)
local httpc = require "httpc.class"
if not content.json then
return json.encode({
code = 400,
msg = "錯誤的呼叫引數",
data = json.null,
})
end
local args = json.decode(content.body)
if type(args) ~= 'table' or type(args['ip_list']) ~= 'table' then
return json.encode({
code = 400,
msg = "錯誤的引數型別",
data = json.null,
})
end
local hc = httpc:new {}
local ret = { code = 200 , data = {}}
for _, ip in ipairs(args['ip_list']) do
local code, response = hc:get("http://freeapi.ipip.net/"..ip)
ret['data'][#ret['data']+1] = json.decode(response)
end
return json.encode(ret)
end)
app:api('/ips_multi', function (content)
local httpc = require "httpc"
if not content.json then
return json.encode({
code = 400,
msg = "錯誤的呼叫引數",
data = json.null,
})
end
local args = json.decode(content.body)
if type(args) ~= 'table' or type(args['ip_list']) ~= 'table' then
return json.encode({
code = 400,
msg = "錯誤的引數型別",
data = json.null,
})
end
local requests = {}
local responses = { code = 200, data = {}}
for _, ip in ipairs(args["ip_list"]) do
requests[#requests+1] = {
domain = "http://freeapi.ipip.net/"..ip,
method = "get",
}
end
local ok, ret = httpc.multi_request(requests)
for _, res in ipairs(ret) do
responses['data'][#responses['data'] + 1] = res
end
return json.encode(responses)
end)
app:listen("", 8090)
app:run()
繼續學習
下一章節我們將學習如何使用htt