skynet源碼分析(16)--skynet中http之httpc和httpd

作者:shihuaping0918@163.com,轉(zhuǎn)載請注明作者

httpc.lua和httpd.lua提供的功能比較簡陋土铺,函數(shù)也比較少丢早,代碼量比較少,一百多行坯辩。在對http協(xié)議有一定認(rèn)識的前提下馁龟,分析這兩個文件的代碼是比較簡單的。

httpc.lua是http客戶端代碼漆魔,支持get/post請求屁柏,發(fā)送完請求以后等待回應(yīng)。并解析回應(yīng)包有送。

local skynet = require "skynet"
local socket = require "http.sockethelper"
local url = require "http.url"
local internal = require "http.internal"
local dns = require "skynet.dns"
local string = string
local table = table

local httpc = {}
--發(fā)送請求并等待回應(yīng)
local function request(fd, method, host, url, recvheader, header, content)
    local read = socket.readfunc(fd)
    local write = socket.writefunc(fd)
    local header_content = ""
    if header then
        if not header.host then
            header.host = host
        end
        for k,v in pairs(header) do --http頭組成字符串
            header_content = string.format("%s%s:%s\r\n", header_content, k, v)
        end
    else
        header_content = string.format("host:%s\r\n",host)
    end

    if content then --有消息體
        local data = string.format("%s %s HTTP/1.1\r\n%scontent-length:%d\r\n\r\n", method, url, header_content, #content) 
--content-length為消息體長度
        write(data)
        write(content)
    else --無消息體
        local request_header = string.format("%s %s HTTP/1.1\r\n%scontent-length:0\r\n\r\n", method, url, header_content)
--content-length為消息體長度淌喻,沒有消息體就為0
        write(request_header)
    end
--等待回應(yīng)
    local tmpline = {}
    local body = internal.recvheader(read, tmpline, "")
    if not body then
        error(socket.socket_error)
    end
--取出狀態(tài)碼,200是ok
    local statusline = tmpline[1]
    local code, info = statusline:match "HTTP/[%d%.]+%s+([%d]+)%s+(.*)$"
    code = assert(tonumber(code))
--取消息頭
    local header = internal.parseheader(tmpline,2,recvheader or {})
    if not header then
        error("Invalid HTTP response header")
    end
--取content-length
    local length = header["content-length"]
    if length then
        length = tonumber(length)
    end
--取消息體編碼方式
    local mode = header["transfer-encoding"]
    if mode then
        if mode ~= "identity" and mode ~= "chunked" then
            error ("Unsupport transfer-encoding")
        end
    end
--讀取消息體
    if mode == "chunked" then
        body, header = internal.recvchunkedbody(read, nil, header, body)
        if not body then
            error("Invalid response body")
        end
    else
        -- identity mode
        if length then
            if #body >= length then
                body = body:sub(1,length)
            else
                local padding = read(length - #body)
                body = body .. padding
            end
        else
            -- no content-length, read all
            body = body .. socket.readall(fd)
        end
    end

    return code, body
end

local async_dns

function httpc.dns(server,port)
    async_dns = true
    dns.server(server,port)
end

function httpc.request(method, host, url, recvheader, header, content)
    local timeout = httpc.timeout   -- get httpc.timeout before any blocked api
    local hostname, port = host:match"([^:]+):?(%d*)$"
    if port == "" then --默認(rèn)端口80
        port = 80
    else
        port = tonumber(port)
    end
--如果是域名雀摘,而不是ip
    if async_dns and not hostname:match(".*%d+$") then
        hostname = dns.resolve(hostname)
    end
--連接服務(wù)器裸删,如果timeout>0,就是異步等待
    local fd = socket.connect(hostname, port, timeout)
    local finish
    if timeout then
        skynet.timeout(timeout, function()
            if not finish then
                socket.shutdown(fd) -- shutdown the socket fd, need close later.
            end
        end)
    end
--調(diào)用上面定義的request函數(shù)阵赠,在保護模式下進行
    local ok , statuscode, body = pcall(request, fd,method, host, url, recvheader, header, content)
    finish = true
    socket.close(fd)
    if ok then
        return statuscode, body
    else
        error(statuscode)
    end
end
--get方法
function httpc.get(...)
    return httpc.request("GET", ...)
end
--轉(zhuǎn)換為百分號表示
local function escape(s)
    return (string.gsub(s, "([^A-Za-z0-9_])", function(c)
        return string.format("%%%02X", string.byte(c))
    end))
end
--post方法
function httpc.post(host, url, form, recvheader)
    local header = {
        ["content-type"] = "application/x-www-form-urlencoded"
    }
    local body = {}
    for k,v in pairs(form) do
        table.insert(body, string.format("%s=%s",escape(k),escape(v)))
    end

    return httpc.request("POST", host, url, recvheader, header, table.concat(body , "&"))
end

return httpc

httpd.lua涯塔,功能只有讀取請求,解析請求清蚀。發(fā)送回應(yīng)匕荸。

local internal = require "http.internal"

local table = table
local string = string
local type = type

local httpd = {}
--錯誤狀態(tài)碼定義
local http_status_msg = {
    [100] = "Continue",
    [101] = "Switching Protocols",
    [200] = "OK",
    [201] = "Created",
    [202] = "Accepted",
    [203] = "Non-Authoritative Information",
    [204] = "No Content",
    [205] = "Reset Content",
    [206] = "Partial Content",
    [300] = "Multiple Choices",
    [301] = "Moved Permanently",
    [302] = "Found",
    [303] = "See Other",
    [304] = "Not Modified",
    [305] = "Use Proxy",
    [307] = "Temporary Redirect",
    [400] = "Bad Request",
    [401] = "Unauthorized",
    [402] = "Payment Required",
    [403] = "Forbidden",
    [404] = "Not Found",
    [405] = "Method Not Allowed",
    [406] = "Not Acceptable",
    [407] = "Proxy Authentication Required",
    [408] = "Request Time-out",
    [409] = "Conflict",
    [410] = "Gone",
    [411] = "Length Required",
    [412] = "Precondition Failed",
    [413] = "Request Entity Too Large",
    [414] = "Request-URI Too Large",
    [415] = "Unsupported Media Type",
    [416] = "Requested range not satisfiable",
    [417] = "Expectation Failed",
    [500] = "Internal Server Error",
    [501] = "Not Implemented",
    [502] = "Bad Gateway",
    [503] = "Service Unavailable",
    [504] = "Gateway Time-out",
    [505] = "HTTP Version not supported",
}
--讀請求
local function readall(readbytes, bodylimit)
    local tmpline = {}
    local body = internal.recvheader(readbytes, tmpline, "")
    if not body then
        return 413  -- Request Entity Too Large
    end
    local request = assert(tmpline[1])
    --請求url/method,http版本號,start line對應(yīng)的內(nèi)容
    local method, url, httpver = request:match "^(%a+)%s+(.-)%s+HTTP/([%d%.]+)$"
    assert(method and url and httpver)
    httpver = assert(tonumber(httpver))
    if httpver < 1.0 or httpver > 1.1 then --http版本錯誤
        return 505  -- HTTP Version not supported
    end
    local header = internal.parseheader(tmpline,2,{})
    if not header then
        return 400  -- Bad request
    end
    local length = header["content-length"] --消息體長度枷邪,所有的field name被轉(zhuǎn)成小寫了
    if length then
        length = tonumber(length)
    end
    local mode = header["transfer-encoding"] --消息體編碼格式
    if mode then
        if mode ~= "identity" and mode ~= "chunked" then
            return 501  -- Not Implemented
        end
    end

    if mode == "chunked" then --chunked方式
        body, header = internal.recvchunkedbody(readbytes, bodylimit, header, body)
        if not body then
            return 413
        end
    else
        -- identity mode
        if length then
            if bodylimit and length > bodylimit then
                return 413
            end
            if #body >= length then
                body = body:sub(1,length)
            else
                local padding = readbytes(length - #body) --讀指定的長度
                body = body .. padding 
            end
        end
    end

    return 200, url, method, header, body
end
--讀取http請求
function httpd.read_request(...)
    local ok, code, url, method, header, body = pcall(readall, ...)
    if ok then
        return code, url, method, header, body
    else
        return nil, code
    end
end
--發(fā)應(yīng)答包
local function writeall(writefunc, statuscode, bodyfunc, header)
     --http start line
    local statusline = string.format("HTTP/1.1 %03d %s\r\n", statuscode, http_status_msg[statuscode] or "")
    writefunc(statusline)
    if header then --發(fā)http頭
        for k,v in pairs(header) do
            if type(v) == "table" then --表中表榛搔,或者表中key對應(yīng)的是數(shù)組
                for _,v in ipairs(v) do
                    writefunc(string.format("%s: %s\r\n", k,v))
                end
            else
                writefunc(string.format("%s: %s\r\n", k,v))
            end
        end
    end
    local t = type(bodyfunc) --wtf,這個名字取得很誤導(dǎo)
    if t == "string" then
        writefunc(string.format("content-length: %d\r\n\r\n", #bodyfunc)) --消息體長度
        writefunc(bodyfunc) --bodyfunc是字符串啊,所以說名字取得很誤導(dǎo)人
    elseif t == "function" then
        writefunc("transfer-encoding: chunked\r\n")
        while true do
            local s = bodyfunc() --取消息體的一部分践惑,應(yīng)該是個generator才對
            if s then
                if s ~= "" then
                    writefunc(string.format("\r\n%x\r\n", #s)) --chunk size
                    writefunc(s) --chunk data
                end
            else
                writefunc("\r\n0\r\n\r\n") --last chunk
                break
            end
        end
    else
        assert(t == "nil")
        writefunc("\r\n")
    end
end

function httpd.write_response(...)
    return pcall(writeall, ...)
end

return httpd

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末腹泌,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子尔觉,更是在濱河造成了極大的恐慌凉袱,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侦铜,死亡現(xiàn)場離奇詭異专甩,居然都是意外死亡,警方通過查閱死者的電腦和手機钉稍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門涤躲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嫁盲,你說我怎么就攤上這事篓叶×衣樱” “怎么了羞秤?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長左敌。 經(jīng)常有香客問我瘾蛋,道長,這世上最難降的妖魔是什么矫限? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任哺哼,我火速辦了婚禮,結(jié)果婚禮上叼风,老公的妹妹穿的比我還像新娘取董。我一直安慰自己,他們只是感情好无宿,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布茵汰。 她就那樣靜靜地躺著,像睡著了一般孽鸡。 火紅的嫁衣襯著肌膚如雪蹂午。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天彬碱,我揣著相機與錄音豆胸,去河邊找鬼。 笑死巷疼,一個胖子當(dāng)著我的面吹牛晚胡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼搬泥,長吁一口氣:“原來是場噩夢啊……” “哼桑寨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起忿檩,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤尉尾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后燥透,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沙咏,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年班套,在試婚紗的時候發(fā)現(xiàn)自己被綠了肢藐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡吱韭,死狀恐怖吆豹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情理盆,我是刑警寧澤痘煤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站猿规,受9級特大地震影響衷快,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜姨俩,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一蘸拔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧环葵,春花似錦调窍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至帝璧,卻和暖如春先誉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背的烁。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工褐耳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人渴庆。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓铃芦,卻偏偏與公主長得像雅镊,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子刃滓,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容