mirror of
https://github.com/cloudwu/skynet.git
synced 2026-07-24 20:23:06 +00:00
Request stream (#1463)
* split request/response * stream api * fix: global variable (#1464) * fix global variable * fix global variable * Add httpc.head Co-authored-by: JTrancender <jie-email@jie-trancender.org>
This commit is contained in:
@@ -141,9 +141,29 @@ function M.recvchunkedbody(readbytes, bodylimit, header, body)
|
||||
return result, header
|
||||
end
|
||||
|
||||
local function recvbody(interface, code, header, body)
|
||||
local length = header["content-length"]
|
||||
if length then
|
||||
length = tonumber(length)
|
||||
end
|
||||
if length then
|
||||
if #body >= length then
|
||||
body = body:sub(1,length)
|
||||
else
|
||||
local padding = interface.read(length - #body)
|
||||
body = body .. padding
|
||||
end
|
||||
elseif code == 204 or code == 304 or code < 200 then
|
||||
body = ""
|
||||
-- See https://stackoverflow.com/questions/15991173/is-the-content-length-header-required-for-a-http-1-0-response
|
||||
else
|
||||
-- no content-length, read all
|
||||
body = body .. interface.readall()
|
||||
end
|
||||
return body
|
||||
end
|
||||
|
||||
function M.request(interface, method, host, url, recvheader, header, content)
|
||||
local is_ws = interface.websocket
|
||||
local read = interface.read
|
||||
local write = interface.write
|
||||
local header_content = ""
|
||||
@@ -181,11 +201,10 @@ function M.request(interface, method, host, url, recvheader, header, content)
|
||||
if not header then
|
||||
error("Invalid HTTP response header")
|
||||
end
|
||||
return code, body, header
|
||||
end
|
||||
|
||||
local length = header["content-length"]
|
||||
if length then
|
||||
length = tonumber(length)
|
||||
end
|
||||
function M.response(interface, code, body, header)
|
||||
local mode = header["transfer-encoding"]
|
||||
if mode then
|
||||
if mode ~= "identity" and mode ~= "chunked" then
|
||||
@@ -194,32 +213,172 @@ function M.request(interface, method, host, url, recvheader, header, content)
|
||||
end
|
||||
|
||||
if mode == "chunked" then
|
||||
body, header = M.recvchunkedbody(read, nil, header, body)
|
||||
body, header = M.recvchunkedbody(interface.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)
|
||||
body = recvbody(interface, code, header, body)
|
||||
end
|
||||
|
||||
return body
|
||||
end
|
||||
|
||||
local stream = {}; stream.__index = stream
|
||||
|
||||
function stream:close()
|
||||
if self._onclose then
|
||||
self._onclose(self)
|
||||
self._onclose = nil
|
||||
end
|
||||
end
|
||||
|
||||
function stream:padding()
|
||||
return self._reading(self), self
|
||||
end
|
||||
|
||||
stream.__close = stream.close
|
||||
stream.__call = stream.padding
|
||||
|
||||
local function stream_nobody(stream)
|
||||
stream._reading = stream.close
|
||||
stream.connected = nil
|
||||
return ""
|
||||
end
|
||||
|
||||
local function stream_length(length)
|
||||
return function(stream)
|
||||
local body = stream._body
|
||||
if body == nil then
|
||||
local ret, padding = interface.read()
|
||||
if not ret then
|
||||
-- disconnected
|
||||
body = padding
|
||||
stream.connected = false
|
||||
else
|
||||
local padding = read(length - #body)
|
||||
body = body .. padding
|
||||
body = ret
|
||||
end
|
||||
elseif code == 204 or code == 304 or code < 200 then
|
||||
body = ""
|
||||
-- See https://stackoverflow.com/questions/15991173/is-the-content-length-header-required-for-a-http-1-0-response
|
||||
elseif is_ws and code == 101 then
|
||||
-- if websocket handshake success
|
||||
return code, body
|
||||
end
|
||||
local n = #body
|
||||
if n >= length then
|
||||
stream._reading = stream.close
|
||||
stream.connected = nil
|
||||
return (body:sub(1,length))
|
||||
else
|
||||
-- no content-length, read all
|
||||
body = body .. interface.readall()
|
||||
length = length - n
|
||||
stream._body = nil
|
||||
if not stream.connected then
|
||||
stream._reading = stream.close
|
||||
end
|
||||
return body
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function stream_read(stream)
|
||||
local ret, padding = stream._interface.read()
|
||||
if ret == "" or not ret then
|
||||
stream.connected = nil
|
||||
stream:close()
|
||||
if padding == "" then
|
||||
return
|
||||
end
|
||||
return padding
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
local function stream_all(stream)
|
||||
local body = stream._body
|
||||
stream._body = nil
|
||||
stream._reading = stream_read
|
||||
return body
|
||||
end
|
||||
|
||||
local function stream_chunked(stream)
|
||||
local read = stream._interface.read
|
||||
local sz, body = chunksize(read, stream._body)
|
||||
if not sz then
|
||||
stream.connected = false
|
||||
stream:close()
|
||||
return
|
||||
end
|
||||
|
||||
if sz == 0 then
|
||||
-- last chunk
|
||||
local tmpline = {}
|
||||
body = M.recvheader(read, tmpline, body)
|
||||
if not body then
|
||||
stream.connected = false
|
||||
stream:close()
|
||||
return
|
||||
end
|
||||
|
||||
M.parseheader(tmpline,1, stream.header)
|
||||
|
||||
stream._reading = stream.close
|
||||
stream.connected = nil
|
||||
return ""
|
||||
end
|
||||
|
||||
local n = #body
|
||||
local remain
|
||||
|
||||
if n >= sz then
|
||||
remain = body:sub(sz+1)
|
||||
body = body:sub(1,sz)
|
||||
else
|
||||
body = body .. read(sz - n)
|
||||
remain = ""
|
||||
end
|
||||
remain = readcrln(read, remain)
|
||||
if not remain then
|
||||
stream.connected = false
|
||||
stream:close()
|
||||
return
|
||||
end
|
||||
stream._body = remain
|
||||
return body
|
||||
end
|
||||
|
||||
function M.response_stream(interface, code, body, header)
|
||||
local mode = header["transfer-encoding"]
|
||||
if mode then
|
||||
if mode ~= "identity" and mode ~= "chunked" then
|
||||
error ("Unsupport transfer-encoding")
|
||||
end
|
||||
end
|
||||
|
||||
return code, body
|
||||
local read_func
|
||||
|
||||
if mode == "chunked" then
|
||||
readfunc = stream_chunked
|
||||
else
|
||||
-- identity mode
|
||||
local length = header["content-length"]
|
||||
if length then
|
||||
length = tonumber(length)
|
||||
end
|
||||
if length then
|
||||
readfunc = stream_length(length)
|
||||
elseif code == 204 or code == 304 or code < 200 then
|
||||
readfunc = stream_nobody
|
||||
else
|
||||
readfunc = stream_all
|
||||
end
|
||||
end
|
||||
|
||||
-- todo: timeout
|
||||
|
||||
return setmetatable({
|
||||
status = code,
|
||||
_body = body,
|
||||
_interface = interface,
|
||||
_reading = readfunc,
|
||||
header = header,
|
||||
connected = true,
|
||||
}, stream)
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
Reference in New Issue
Block a user