Lua Web快速开发指南(7) - 高效的接口调用 - httpc库

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

httpc支持header、args、body、timeout请求设置, 完美支持各种httpc调用方式.

httpc库使用前需要手动导入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设置;

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为错误信息.

什么是一次性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所示.

hchttpc拥有相同的API, 但是需要使用不同的调用方式. 如: hc:gethc:posthc:jsonhc:file.

一旦hc使用完毕时, 需要显示的调用hc:close()方法来关闭创建的httpc对象并销毁httpc的连接.

1. 启动一个httpd库的web server

main.lua中启动一个httpd的server.

1. 增加一个API路由用于ip地址归属地查询

我们先利用httpd库启动一个server服务, 并且对外提供IP归属地查询接口

  1. app:api('/ip', function(content)
  2. local httpc = require "httpc"
  3. local args = content.args
  4. if not args or not args['ip'] then
  5. return json.encode({
  6. code = 400,
  7. msg = "错误的接口调用方式",
  8. data = json.null,
  9. })
  10. end
  11. local code, response = httpc.get("http://freeapi.ipip.net/"..args["ip"])
  12. if code ~= 200 then
  13. return json.encode({
  14. code = 401,
  15. msg = "获取数据失败",
  16. data = json.null,
  17. })
  18. end
  19. return response
  20. end)

现在代码已经完成! 让我们打开浏览器输入:http://localhost:8080/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的归属地信息一次性返回给客户端.

  1. app:api('/ips', function(content)
  2. local httpc = require "httpc.class"
  3. if not content.json then
  4. return json.encode({
  5. code = 400,
  6. msg = "错误的调用参数",
  7. data = json.null,
  8. })
  9. end
  10. local args = json.decode(content.body)
  11. if type(args) ~= 'table' or type(args['ip_list']) ~= 'table' then
  12. return json.encode({
  13. code = 400,
  14. msg = "错误的参数类型",
  15. data = json.null,
  16. })
  17. end
  18. local hc = httpc:new {}
  19. local ret = { code = 200 , data = {}}
  20. for _, ip in ipairs(args['ip_list']) do
  21. local code, response = hc:get("http://freeapi.ipip.net/"..ip)
  22. ret['data'][#ret['data']+1] = json.decode(response)
  23. return json.encode(ret)
  24. end)

由于普通浏览器POST无法发送json, 让我们使用curl命令行工具进行测试:

返回数据如下:

    3. 持续优化.

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

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

    这样的情况下, httpc库提供了一个叫multi_request的方法. 具体使用方法在.

    这个方法可以让我们同时发送几十上百个请求来解决单个连接阻塞的问题.

    4. 并发请求

    现在, 让我使用httpc库的multi_request方法来并发请求多个接口, 减少连接阻塞带来的问题.

    1. app:api('/ips_multi', function (content)
    2. local httpc = require "httpc"
    3. if not content.json then
    4. return json.encode({
    5. code = 400,
    6. msg = "错误的调用参数",
    7. data = json.null,
    8. })
    9. end
    10. local args = json.decode(content.body)
    11. if type(args) ~= 'table' or type(args['ip_list']) ~= 'table' then
    12. return json.encode({
    13. code = 400,
    14. msg = "错误的参数类型",
    15. data = json.null,
    16. })
    17. end
    18. local requests = {}
    19. local responses = { code = 200, data = {}}
    20. for _, ip in ipairs(args["ip_list"]) do
    21. requests[#requests+1] = {
    22. domain = "http://freeapi.ipip.net/"..ip,
    23. method = "get",
    24. }
    25. end
    26. local ok, ret = httpc.multi_request(requests)
    27. for _, res in ipairs(ret) do
    28. responses['data'][#responses['data'] + 1] = res
    29. end
    30. return json.encode(responses)
    31. end)

    好的, 现在让我们再次使用curl工具进行测试:

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

    1. [candy@MacBookPro:~/Documents/core_framework] $ ./cfadmin
    2. [2019/06/16 17:45:21] [INFO] httpd正在监听: 0.0.0.0:8080
    3. [2019/06/16 17:45:21] [INFO] httpd正在运行Web Server服务...
    4. [2019/06/16 17:45:23] - ::1 - ::1 - /ips_multi - POST - 200 - req_time: 0.140253/Sec
    5. [2019/06/16 17:45:38] - ::1 - ::1 - /ips - POST - 200 - req_time: 0.288286/Sec
    1. local httpd = require "httpd"
    2. local json = require "json"
    3. local app = httpd:new("httpd")
    4. app:api('/ip', function(content)
    5. local httpc = require "httpc"
    6. local args = content.args
    7. if not args or not args['ip'] then
    8. return json.encode({
    9. code = 400,
    10. msg = "错误的接口调用方式",
    11. data = json.null,
    12. })
    13. end
    14. local code, response = httpc.get("http://freeapi.ipip.net/"..args["ip"])
    15. if code ~= 200 then
    16. return json.encode({
    17. code = 401,
    18. msg = "获取数据失败",
    19. })
    20. end
    21. return response
    22. end)
    23. app:api('/ips', function(content)
    24. local httpc = require "httpc.class"
    25. if not content.json then
    26. return json.encode({
    27. code = 400,
    28. msg = "错误的调用参数",
    29. data = json.null,
    30. })
    31. end
    32. local args = json.decode(content.body)
    33. if type(args) ~= 'table' or type(args['ip_list']) ~= 'table' then
    34. return json.encode({
    35. code = 400,
    36. msg = "错误的参数类型",
    37. data = json.null,
    38. })
    39. end
    40. local hc = httpc:new {}
    41. local ret = { code = 200 , data = {}}
    42. for _, ip in ipairs(args['ip_list']) do
    43. local code, response = hc:get("http://freeapi.ipip.net/"..ip)
    44. ret['data'][#ret['data']+1] = json.decode(response)
    45. end
    46. return json.encode(ret)
    47. end)
    48. app:api('/ips_multi', function (content)
    49. local httpc = require "httpc"
    50. if not content.json then
    51. return json.encode({
    52. code = 400,
    53. msg = "错误的调用参数",
    54. data = json.null,
    55. })
    56. end
    57. local args = json.decode(content.body)
    58. if type(args) ~= 'table' or type(args['ip_list']) ~= 'table' then
    59. return json.encode({
    60. code = 400,
    61. msg = "错误的参数类型",
    62. data = json.null,
    63. })
    64. end
    65. local requests = {}
    66. local responses = { code = 200, data = {}}
    67. for _, ip in ipairs(args["ip_list"]) do
    68. requests[#requests+1] = {
    69. domain = "http://freeapi.ipip.net/"..ip,
    70. method = "get",
    71. }
    72. end
    73. local ok, ret = httpc.multi_request(requests)
    74. for _, res in ipairs(ret) do
    75. responses['data'][#responses['data'] + 1] = res
    76. end
    77. return json.encode(responses)
    78. end)
    79. app:listen("", 8080)