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

2019-06-18 22:53 更新

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

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

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

API介紹

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

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

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

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

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

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

TIMEOUT為httpc請(qǐng)求的最大超時(shí)時(shí)間;

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

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

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

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

BODY是一個(gè)key-value數(shù)組, 對(duì)于POST方法將會(huì)自動(dòng)格式化為:body[n][1]=body[n][2]&body[n+1][1]=body[n+1][2];

TIMEOUT為httpc請(qǐng)求的最大超時(shí)時(shí)間;

httpc.json(domain, HEADER, JSON, TIMEOUT)

json方法將會(huì)對(duì)domain發(fā)起一次http POST請(qǐng)求. 此方法的content-type會(huì)被設(shè)置為:application/json.

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

JSON必須是一個(gè)字符串類型;

TIMEOUT為httpc請(qǐng)求的最大超時(shí)時(shí)間;

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

file方法將會(huì)對(duì)domain發(fā)起一次http POST請(qǐng)求.

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

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

TIMEOUT為httpc請(qǐng)求的最大超時(shí)時(shí)間;

httpc 返回值

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

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

"一次性HTTP請(qǐng)求"

什么是一次性httpc請(qǐng)求呢?

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

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

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

httpc庫(kù)的class對(duì)象使用介紹

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

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

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

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

開始實(shí)踐

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

1. 啟動(dòng)一個(gè)httpd庫(kù)的web server

main.lua中啟動(dòng)一個(gè)httpd的server.

  local httpd = require "httpd"
  local json = require "json"


  local app = httpd:new("httpd")




  app:listen("", 8080)


  app:run()

1. 增加一個(gè)API路由用于ip地址歸屬地查詢

我們先利用httpd庫(kù)啟動(dòng)一個(gè)server服務(wù), 并且對(duì)外提供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 = "錯(cuò)誤的接口調(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查看返回?cái)?shù)據(jù).

2. 查詢多個(gè)IP地址的歸屬地

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

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

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

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

  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

返回?cái)?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次請(qǐng)求, 這樣已經(jīng)縮短了請(qǐng)求50%的連接消耗(TCP握手).

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

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

這個(gè)方法可以讓我們同時(shí)發(fā)送幾十上百個(gè)請(qǐng)求來(lái)解決單個(gè)連接阻塞的問題.

4. 并發(fā)請(qǐng)求

現(xiàn)在, 讓我使用httpc庫(kù)的multi_request方法來(lái)并發(fā)請(qǐng)求多個(gè)接口, 減少連接阻塞帶來(lái)的問題.

  app:api('/ips_multi', function (content)
    local httpc = require "httpc"
    if not content.json then
        return json.encode({
            code = 400,
            msg  = "錯(cuò)誤的調(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  = "錯(cuò)誤的參數(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)在讓我們?cè)俅问褂?code>curl工具進(jìn)行測(cè)試:

  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的請(qǐng)求回應(yīng)時(shí)間看到, 響應(yīng)時(shí)間消耗再次降低了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 = "錯(cuò)誤的接口調(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  = "錯(cuò)誤的調(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  = "錯(cuò)誤的參數(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  = "錯(cuò)誤的調(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  = "錯(cuò)誤的參數(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庫(kù)編寫Websocket.

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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)