高效的接口调用 - httpc库


httpc库基于cf框架都内部实现的异步IO编写的http client库.

httpc库内置SSL支持, 在不使用代理的情况下就可以请求第三方接口.

httpc支持headerargsbodytimeout请求设置, 完美支持各种httpc调用方式.

一、httpc介绍


使用前需要手动导入httpc库: local httpc = require "httpc".

1. httpc.get


函数原型: httpc.get(domain, headers, args, timeout)

参数分别是:

  • domain是一个符合URL定义规范的字符串;

  • headers是请求头部, 一般用于添加自定义头部, 一般为结构为: {{key1, value1}, {key2, value2} ... };

  • args是请求参数; 对于{{"a", 1}, {"b", 2}}这样的数据将会格式化为: a=1&b=2;

  • timeout定义了请求的超时时间, 参数的类型为Number;

2. httpc.post


函数原型: httpc.post(domain, headers, body, timeout)

参数分别是:

  • domain是一个符合URL定义规范的字符串;

  • headers是请求头部, 一般用于添加自定义头部, 一般为结构为: {{key1, value1}, {key2, value2} ... };

  • body是请求体; 对于{{"a", 1}, {"b", 2}}这样的数据将会格式化为: a=1&b=2;

  • timeout定义了请求的超时时间, 参数的类型为Number;

    需要注意的是: 这个API发送的请求方法是POST, Content-Typeapplication/x-www-form-urlencoded.

3. httpc.json


函数原型: httpc.json(domain, headers, json, timeout)

参数分别是:

  • domain是一个符合URL定义规范的字符串;

  • headers是请求头部, 一般用于添加自定义头部, 一般为结构为: {{key1, value1}, {key2, value2} ... };

  • json是请求体; 内部是一个合法json或者合法table即可(不可包含NULL/function/userdata);

  • timeout定义了请求的超时时间, 参数的类型为Number;

    需要注意的是: 这个API发送的请求方法是POST, Content-Typeapplication/json.

4. httpc.file


函数原型: httpc.json(domain, headers, json, timeout)

参数分别是:

  • domain是一个符合URL定义规范的字符串;

  • headers是请求头部, 一般用于添加自定义头部, 一般为结构为: {{key1, value1}, {key2, value2} ... };

  • json是请求体; 内部是一个合法json或者合法table即可(不可包含NULL/function/userdata);

  • timeout定义了请求的超时时间, 参数的类型为Number;

    需要注意的是: 这个API发送的请求方法是POST, Content-Typeapplication/json.

二、"一次性请求"


什么是一次性请求呢? 每次我们使用httpc库在请求其他服务的接口, 都会在接口返回后关闭连接.

这在日常使用中也不是大问题, 但是我们需要多次请求同一个接口的时候. 如果不进行一些连接复用, 显然不是那么高效.

框架为了解决这个问题, 提出了使用httpc库的扩展 - httpc.class来完成的方法.

三、httpc.class使用


要使用httpc.class需要导入httpc的class库, 导入方式为: local httpc = require "httpc.class".

当需要使用httpc.class发起请求之前, 需要先创建一个对象, 如: local hc = httpc:new({}).

hchttpc拥有相同的方法, 但是需要hc必须要使用":"的调用方式. 否则框架会抛出异常.

并且hc对象在使用完毕后, 必须使用hc:close()方法来显示关闭; 否则会造成内存泄漏的问题.

四、开始实践


现在, 让我们尝试将上面学到的内容运用到实践中.

1. 编写httpd server


local httpd = require "httpd"
local app = httpd:new("httpd")


app:listen("0.0.0.0", 8080)
app:run()

2. 增加接口路由


在前面创建的httpd server内编写下面的代码, 增加一个API路由用于提供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:8080/ip?ip=8.8.8.8查看返回数据.

3. 接口修改


一个请求对应一次回是HTTP协议的本质! 但是我们经常会遇到批量请求的业务场景, 我们就以此来设计一个批量请求/返回的例子.

让我们假设客户端将会发送一次POST请求, bodyjson类型并且里面包含一个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
  hc:close()
  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:8080/ip

返回数据如下:

{"code":200,"data":[["CLOUDFLARE.COM","CLOUDFLARE.COM","","",""],["GOOGLE.COM","GOOGLE.COM","","","level3.com"],["114DNS.COM","114DNS.COM","","",""]]}

上述例子似乎已经非常完美! 我们利用连接保持的方式进行了3次请求, 这样已经缩短了请求50%的连接消耗(TCP握手).

但是对于非常需要性能的我们来说: 每次请求需要等到上一个请求处理完毕后才能继续发起新的请求, 这样的方式显然还不足以满足我们.

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:8080/ips_multi

我们可以从请求回应时间看到, 响应时间消耗再次降低了50%.

[candy@MacBookPro:~/cfadmin] $ ./cfadmin
[2020/08/16 17:45:21] [INFO] httpd正在监听: 0.0.0.0:8080
[2020/08/16 17:45:21] [INFO] httpd正在运行Web Server服务...
[2020/08/16 17:45:23] - ::1 - ::1 - /ips_multi - POST - 200 - req_time: 0.140253/Sec
[2020/08/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("0.0.0.0", 8080)

app:run()

五、继续学习


本章节我们学会了如何链式调用http服务, 下一章节我们将学习如何使用httpd库编写Websocket.

Copyright © CandyMi 2019-2022 all right reserved,powered by Gitbook该文件修订时间: 2021-11-19 22:05:26

results matching ""

    No results matching ""