Lua Web快速開發(fā)指南(7) - 高效的接口調(diào)用 - httpc庫

2019-06-18 22:53 更新

httpc庫基于cf框架都內(nèi)部實(shí)現(xiàn)的socket編寫的http client庫.

httpc庫內(nèi)置SSL支持, 在不使用代理的情況下就可以請求第三方接口.

httpc支持header、args、body、timeout請求設(shè)置, 完美支持各種httpc調(diào)用方式.

API介紹

httpc庫使用前需要手動導(dǎo)入httpc庫: local httpc = require "httpc".

httpc.get(domain, HEADER, ARGS, TIMEOUT)

調(diào)用get方法將會對domain發(fā)起一次HTTP GET請求.

domain是一個符合URL定義規(guī)范的字符串;

HEADER是一個key-value數(shù)組, 一般用于添加自定義頭部;

ARGS為請求參數(shù)的key-value數(shù)組, 對于GET方法將會自動格式化為:args[n][1]=args[n][2]&args[n+1][1]=args[n+1][2];

TIMEOUT為httpc請求的最大超時時間;

httpc.post(domain, HEADER, BODY, TIMEOUT)

調(diào)用post方法將會對domain發(fā)起一次HTTP POST請求, 此方法的content-type會被設(shè)置為:application/x-www-form-urlencoded.

domain是一個符合URL定義規(guī)范的字符串;

HEADER是一個key-value數(shù)組, 一般用于添加自定義頭部; 不支持Content-Type與Content-Length設(shè)置;

BODY是一個key-value數(shù)組, 對于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發(fā)起一次http POST請求. 此方法的content-type會被設(shè)置為:application/json.

HEADER是一個key-value數(shù)組, 一般用于添加自定義頭部; 不支持Content-Type與Content-Length設(shè)置;

JSON必須是一個字符串類型;

TIMEOUT為httpc請求的最大超時時間;

httpc.file(domain, HEADER, FILES, TIMEOUT)

file方法將會對domain發(fā)起一次http POST請求.

HEADER是一個key-value數(shù)組, 一般用于添加自定義頭部; 不支持Content-Type與Content-Length設(shè)置;

FILES是一個key-value數(shù)組, 每個item包含: name(名稱), filename(文件名), file(文件內(nèi)容), type(文件類型)等屬性. 文件類型可選.

TIMEOUT為httpc請求的最大超時時間;

httpc 返回值

所有httpc請求接口均會有2個返回值: code, response. code為http協(xié)議狀態(tài)碼, response為回應(yīng)body(字符串類型).

參數(shù)不正確, 連接被斷開等其它錯誤, code將會為nil, response為錯誤信息.

"一次性HTTP請求"

什么是一次性httpc請求呢?

每次我們使用httpc庫在請求第三方http接口的時候, 都會在接口返回后關(guān)閉連接. 這在日常使用中一邊也沒什么問題.

但是當(dāng)我們需要多次請求同一個接口的時候, 每次請求完畢就關(guān)閉連接顯然不是那么高效, 現(xiàn)在我們嘗試使用一個http class對象來解決這個問題.

注意: httpc class對象不能對不同域名的接口使用同一個連接, 這會返回一個錯誤調(diào)用給使用者.

httpc庫的class對象使用介紹

要使用httpc的class需要導(dǎo)入httpc的class庫, 導(dǎo)入方式為: local httpc = require "httpc.class".

當(dāng)需要使用httpc發(fā)起請求之前, 需要先創(chuàng)建一個httpc的對象, 如: local hc = httpc:new {}. httpc對象創(chuàng)建與初始化完畢后, 使用方式同上述API所示.

hchttpc擁有相同的API, 但是需要使用不同的調(diào)用方式. 如: hc:get、hc:posthc:json、hc:file.

一旦hc使用完畢時, 需要顯示的調(diào)用hc:close()方法來關(guān)閉創(chuàng)建的httpc對象并銷毀httpc的連接.

開始實(shí)踐

現(xiàn)在, 讓我們將上面學(xué)到的API使用方式運(yùn)用到實(shí)踐中.

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服務(wù), 并且對外提供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 = "錯誤的接口調(diào)用方式",
            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 = "獲取數(shù)據(jù)失敗",
            data = json.null,
            })
    end
    return response
  end)

現(xiàn)在代碼已經(jīng)完成! 讓我們打開瀏覽器輸入:http://localhost:8090/ip?ip=8.8.8.8查看返回數(shù)據(jù).

2. 查詢多個IP地址的歸屬地

一個請求對應(yīng)一次回是HTTP協(xié)議的本質(zhì)! 但是我們經(jīng)常會遇到批量請求的業(yè)務(wù)場景, 我們就以此來設(shè)計一個批量請求/返回的例子.

讓我們假設(shè)客戶端將會發(fā)送一次POST請求, body為json類型并且里面包含一個IP數(shù)組: ip_list = {1.1.1.1, 8.8.8.8, 114.114.114.114}.

服務(wù)端在接受到這個數(shù)組之后, 需要將這ip的歸屬地信息一次性返回給客戶端.

  app:api('/ips', function(content)
    local httpc = require "httpc.class"
    if not content.json then
        return json.encode({
            code = 400,
            msg  = "錯誤的調(diào)用參數(shù)",
            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  = "錯誤的參數(shù)類型",
            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無法發(fā)送json, 讓我們使用curl命令行工具進(jìn)行測試:

  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

返回數(shù)據(jù)如下:

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

3. 持續(xù)優(yōu)化.

上述例子似乎已經(jīng)非常完美! 我們利用連接保持的方式進(jìn)行了3次請求, 這樣已經(jīng)縮短了請求50%的連接消耗(TCP握手).

但是對于非常需要性能的我們來說: 每次請求需要等到上一個請求處理完畢后才能繼續(xù)發(fā)起新的請求, 這樣的方式顯然還不足以滿足我們.

這樣的情況下, httpc庫提供了一個叫multi_request的方法. 具體使用方法在這里.

這個方法可以讓我們同時發(fā)送幾十上百個請求來解決單個連接阻塞的問題.

4. 并發(fā)請求

現(xiàn)在, 讓我使用httpc庫的multi_request方法來并發(fā)請求多個接口, 減少連接阻塞帶來的問題.

  app:api('/ips_multi', function (content)
    local httpc = require "httpc"
    if not content.json then
        return json.encode({
            code = 400,
            msg  = "錯誤的調(diào)用參數(shù)",
            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  = "錯誤的參數(shù)類型",
            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)

好的, 現(xiàn)在讓我們再次使用curl工具進(jìn)行測試:

  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的請求回應(yīng)時間看到, 響應(yīng)時間消耗再次降低了50%.

  [candy@MacBookPro:~/Documents/core_framework] $ ./cfadmin
  [2019/06/16 17:45:21] [INFO] httpd正在監(jiān)聽: 0.0.0.0:8090
  [2019/06/16 17:45:21] [INFO] httpd正在運(yùn)行Web Server服務(wù)...
  [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 = "錯誤的接口調(diào)用方式",
            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 = "獲取數(shù)據(jù)失敗",
            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  = "錯誤的調(diào)用參數(shù)",
            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  = "錯誤的參數(shù)類型",
            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  = "錯誤的調(diào)用參數(shù)",
            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  = "錯誤的參數(shù)類型",
            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()

繼續(xù)學(xué)習(xí)

下一章節(jié)我們將學(xué)習(xí)如何使用httpd庫編寫Websocket.

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號