From dec2e6fe4c1fd0d21a1bfe82f9649b4073395d5d Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Mon, 4 Aug 2014 18:06:16 +0800 Subject: [PATCH] add httpc --- examples/simpleweb.lua | 5 ++ lualib/http/httpc.lua | 114 ++++++++++++++++++++++++++++++++ lualib/http/httpd.lua | 136 ++------------------------------------- lualib/http/internal.lua | 135 ++++++++++++++++++++++++++++++++++++++ test/testhttp.lua | 16 +++++ 5 files changed, 276 insertions(+), 130 deletions(-) create mode 100644 lualib/http/httpc.lua create mode 100644 lualib/http/internal.lua create mode 100644 test/testhttp.lua diff --git a/examples/simpleweb.lua b/examples/simpleweb.lua index 19870aba..234c2d08 100644 --- a/examples/simpleweb.lua +++ b/examples/simpleweb.lua @@ -39,6 +39,11 @@ skynet.start(function() table.insert(tmp, string.format("query: %s= %s", k,v)) end end + table.insert(tmp, "-----header----") + for k,v in pairs(header) do + table.insert(tmp, string.format("%s = %s",k,v)) + end + table.insert(tmp, "-----body----\n" .. body) response(id, code, table.concat(tmp,"\n")) end else diff --git a/lualib/http/httpc.lua b/lualib/http/httpc.lua new file mode 100644 index 00000000..784b186b --- /dev/null +++ b/lualib/http/httpc.lua @@ -0,0 +1,114 @@ +local socket = require "http.sockethelper" +local url = require "http.url" +local internal = require "http.internal" +local string = string +local table = table + +local httpc = {} + +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 + for k,v in pairs(header) do + header_content = string.format("%s%s:%s\r\n", header_content, k, v) + end + end + + if content then + local data = string.format("%s %s HTTP/1.1\r\nhost:%s\r\ncontent-length:%d\r\n%s\r\n%s", method, url, host, #content, header_content, content) + write(data) + else + local request_header = string.format("%s %s HTTP/1.1\r\nhost:%s\r\ncontent-length:0\r\n%s\r\n", method, url, host, header_content) + write(request_header) + end + + local tmpline = {} + local body = internal.recvheader(read, tmpline, "") + if not body then + error(socket.socket_error) + end + + 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 + + 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 + body = nil + end + end + + return code, body +end + +function httpc.request(method, host, url, recvheader, header, content) + local hostname, port = host:match"([^:]+):?(%d*)$" + if port == "" then + port = 80 + else + port = tonumber(port) + end + local fd = socket.connect(hostname, port) + local ok , statuscode, body = pcall(request, fd,method, host, url, recvheader, header, content) + if ok then + return statuscode, body + else + socket.close(fd) + error(statuscode) + end +end + +function httpc.get(...) + return httpc.request("GET", ...) +end + +local function escape(s) + return (string.gsub(s, "([^A-Za-z0-9_])", function(c) + return string.format("%%%02X", string.byte(c)) + end)) +end + +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 diff --git a/lualib/http/httpd.lua b/lualib/http/httpd.lua index 190b6b93..5f8f6de8 100644 --- a/lualib/http/httpd.lua +++ b/lualib/http/httpd.lua @@ -1,3 +1,5 @@ +local internal = require "http.internal" + local table = table local httpd = {} @@ -45,135 +47,9 @@ local http_status_msg = { [505] = "HTTP Version not supported", } -local function recvheader(readbytes, limit, lines, header) - local result - local e = header:find("\r\n\r\n", 1, true) - if e then - result = header:sub(e+4) - else - while true do - local bytes = readbytes() - header = header .. bytes - if #header > limit then - return - end - e = header:find("\r\n\r\n", -#bytes-3, true) - if e then - result = header:sub(e+4) - break - end - end - end - for v in header:gmatch("(.-)\r\n") do - if v == "" then - break - end - table.insert(lines, v) - end - return result -end - -local function parseheader(lines, from, header) - local name, value - for i=from,#lines do - local line = lines[i] - if line:byte(1) == 9 then -- tab, append last line - if name == nil then - return - end - header[name] = header[name] .. line:sub(2) - else - name, value = line:match "^(.-):%s*(.*)" - if name == nil or value == nil then - return - end - name = name:lower() - if header[name] then - header[name] = header[name] .. ", " .. value - else - header[name] = value - end - end - end - return header -end - -local function chunksize(readbytes, body) - while true do - if #body > 128 then - return - end - body = body .. readbytes() - local f,e = body:find("\r\n",1,true) - if f then - return tonumber(body:sub(1,f-1),16), body:sub(e+1) - end - end -end - -local function readcrln(readbytes, body) - if #body > 2 then - if body:sub(1,2) ~= "\r\n" then - return - end - return body:sub(3) - else - body = body .. readbytes(2-#body) - if body ~= "\r\n" then - return - end - return "" - end -end - -local function recvchunkedbody(readbytes, bodylimit, header, body) - local result = "" - local size = 0 - - while true do - local sz - sz , body = chunksize(readbytes, body) - if not sz then - return - end - if sz == 0 then - break - end - size = size + sz - if bodylimit and size > bodylimit then - return - end - if #body >= sz then - result = result .. body:sub(1,sz) - body = body:sub(sz+1) - else - result = result .. body .. readbytes(sz - #body) - body = "" - end - body = readcrln(readbytes, body) - if not body then - return - end - end - - body = readcrln(readbytes, body) - if not body then - return - end - local tmpline = {} - body = recvheader(readbytes, 8192, tmpline, body) - if not body then - return - end - - header = parseheader(tmpline,1,header) - - return result, header -end - local function readall(readbytes, bodylimit) local tmpline = {} - local body = recvheader(readbytes, 8192, tmpline, "") + local body = internal.recvheader(readbytes, tmpline, "") if not body then return 413 -- Request Entity Too Large end @@ -184,7 +60,7 @@ local function readall(readbytes, bodylimit) if httpver < 1.0 or httpver > 1.1 then return 505 -- HTTP Version not supported end - local header = parseheader(tmpline,2,{}) + local header = internal.parseheader(tmpline,2,{}) if not header then return 400 -- Bad request end @@ -194,13 +70,13 @@ local function readall(readbytes, bodylimit) end local mode = header["transfer-encoding"] if mode then - if mode ~= "identity" or mode ~= "chunked" then + if mode ~= "identity" and mode ~= "chunked" then return 501 -- Not Implemented end end if mode == "chunked" then - body, header = recvchunkedbody(readbytes, bodylimit, header, body) + body, header = internal.recvchunkedbody(readbytes, bodylimit, header, body) if not body then return 413 end diff --git a/lualib/http/internal.lua b/lualib/http/internal.lua new file mode 100644 index 00000000..067152ad --- /dev/null +++ b/lualib/http/internal.lua @@ -0,0 +1,135 @@ +local M = {} + +local LIMIT = 8192 + +local function chunksize(readbytes, body) + while true do + if #body > 128 then + return + end + body = body .. readbytes() + local f,e = body:find("\r\n",1,true) + if f then + return tonumber(body:sub(1,f-1),16), body:sub(e+1) + end + end +end + +local function readcrln(readbytes, body) + if #body >= 2 then + if body:sub(1,2) ~= "\r\n" then + return + end + return body:sub(3) + else + body = body .. readbytes(2-#body) + if body ~= "\r\n" then + return + end + return "" + end +end + +function M.recvheader(readbytes, lines, header) + if #header >= 2 then + if header:find "^\r\n" then + return header:sub(3) + end + end + local result + local e = header:find("\r\n\r\n", 1, true) + if e then + result = header:sub(e+4) + else + while true do + local bytes = readbytes() + header = header .. bytes + if #header > LIMIT then + return + end + e = header:find("\r\n\r\n", -#bytes-3, true) + if e then + result = header:sub(e+4) + break + end + if header:find "^\r\n" then + return header:sub(3) + end + end + end + for v in header:gmatch("(.-)\r\n") do + if v == "" then + break + end + table.insert(lines, v) + end + return result +end + +function M.parseheader(lines, from, header) + local name, value + for i=from,#lines do + local line = lines[i] + if line:byte(1) == 9 then -- tab, append last line + if name == nil then + return + end + header[name] = header[name] .. line:sub(2) + else + name, value = line:match "^(.-):%s*(.*)" + if name == nil or value == nil then + return + end + name = name:lower() + if header[name] then + header[name] = header[name] .. ", " .. value + else + header[name] = value + end + end + end + return header +end + +function M.recvchunkedbody(readbytes, bodylimit, header, body) + local result = "" + local size = 0 + + while true do + local sz + sz , body = chunksize(readbytes, body) + if not sz then + return + end + if sz == 0 then + break + end + size = size + sz + if bodylimit and size > bodylimit then + return + end + if #body >= sz then + result = result .. body:sub(1,sz) + body = body:sub(sz+1) + else + result = result .. body .. readbytes(sz - #body) + body = "" + end + body = readcrln(readbytes, body) + if not body then + return + end + end + + local tmpline = {} + body = M.recvheader(readbytes, tmpline, body) + if not body then + return + end + + header = M.parseheader(tmpline,1,header) + + return result, header +end + +return M diff --git a/test/testhttp.lua b/test/testhttp.lua new file mode 100644 index 00000000..52abbe4b --- /dev/null +++ b/test/testhttp.lua @@ -0,0 +1,16 @@ +local skynet = require "skynet" +local httpc = require "http.httpc" + +skynet.start(function() + print("GET www.baidu.com") + local header = {} + local status, body = httpc.get("baidu.com", "/", header) + print("[header] =====>") + for k,v in pairs(header) do + print(k,v) + end + print("[body] =====>", status) + print(body) + + skynet.exit() +end)