共计 6392 个字符,预计需要花费 16 分钟才能阅读完成。
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()
继续学习
下一章节我们将学习如何使用 httpd 库编写 Websocket.