Merge pull request #116 from dvv/master
http module draft, with example, by @dvv
This commit is contained in:
commit
b6a4030102
|
@ -0,0 +1,41 @@
|
|||
------------------------------------------------------------------------------
|
||||
-- HTTP server Hello world example
|
||||
--
|
||||
-- LICENCE: http://opensource.org/licenses/MIT
|
||||
-- Vladimir Dronnikov <dronnikov@gmail.com>
|
||||
------------------------------------------------------------------------------
|
||||
require("http").createServer(80, function(req, res)
|
||||
-- analyse method and url
|
||||
print("+R", req.method, req.url)
|
||||
-- setup handler of headers, if any
|
||||
--[[
|
||||
req.onheader = function(self, name, value)
|
||||
-- print("+H", name, value)
|
||||
-- E.g. look for "content-type" header,
|
||||
-- setup body parser to particular format
|
||||
-- if name == "content-type" then
|
||||
-- if value == "application/json" then
|
||||
-- req.ondata = function(self, chunk) ... end
|
||||
-- elseif value == "application/x-www-form-urlencoded" then
|
||||
-- req.ondata = function(self, chunk) ... end
|
||||
-- end
|
||||
-- end
|
||||
end
|
||||
-- setup handler of body, if any
|
||||
req.ondata = function(self, chunk)
|
||||
print("+B", chunk and #chunk, node.heap())
|
||||
-- request ended?
|
||||
if not chunk then
|
||||
-- reply
|
||||
--res:finish("")
|
||||
res:send(nil, 200)
|
||||
res:send_header("Connection", "close")
|
||||
res:send("Hello, world!")
|
||||
res:finish()
|
||||
end
|
||||
end
|
||||
]]
|
||||
-- or just do something not waiting till body (if any) comes
|
||||
--res:finish("Hello, world!")
|
||||
res:finish("Salut, monde!")
|
||||
end)
|
|
@ -0,0 +1,216 @@
|
|||
------------------------------------------------------------------------------
|
||||
-- HTTP server module
|
||||
--
|
||||
-- LICENCE: http://opensource.org/licenses/MIT
|
||||
-- Vladimir Dronnikov <dronnikov@gmail.com>
|
||||
------------------------------------------------------------------------------
|
||||
local collectgarbage, tonumber, tostring = collectgarbage, tonumber, tostring
|
||||
|
||||
local http
|
||||
do
|
||||
------------------------------------------------------------------------------
|
||||
-- request methods
|
||||
------------------------------------------------------------------------------
|
||||
local make_req = function(conn, method, url)
|
||||
local req = {
|
||||
conn = conn,
|
||||
method = method,
|
||||
url = url,
|
||||
}
|
||||
-- return setmetatable(req, {
|
||||
-- })
|
||||
return req
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
-- response methods
|
||||
------------------------------------------------------------------------------
|
||||
local send = function(self, data, status)
|
||||
local c = self.conn
|
||||
-- TODO: req.send should take care of response headers!
|
||||
if self.send_header then
|
||||
c:send("HTTP/1.1 ")
|
||||
c:send(tostring(status or 200))
|
||||
-- TODO: real HTTP status code/name table
|
||||
c:send(" OK\r\n")
|
||||
-- we use chunked transfer encoding, to not deal with Content-Length:
|
||||
-- response header
|
||||
self:send_header("Transfer-Encoding", "chunked")
|
||||
-- TODO: send standard response headers, such as Server:, Date:
|
||||
end
|
||||
if data then
|
||||
-- NB: no headers allowed after response body started
|
||||
if self.send_header then
|
||||
self.send_header = nil
|
||||
-- end response headers
|
||||
c:send("\r\n")
|
||||
end
|
||||
-- chunked transfer encoding
|
||||
c:send(("%X\r\n"):format(#data))
|
||||
c:send(data)
|
||||
c:send("\r\n")
|
||||
end
|
||||
end
|
||||
local send_header = function(self, name, value)
|
||||
local c = self.conn
|
||||
-- NB: quite a naive implementation
|
||||
c:send(name)
|
||||
c:send(": ")
|
||||
c:send(value)
|
||||
c:send("\r\n")
|
||||
end
|
||||
-- finalize request, optionally sending data
|
||||
local finish = function(self, data, status)
|
||||
local c = self.conn
|
||||
-- NB: req.send takes care of response headers
|
||||
if data then
|
||||
self:send(data, status)
|
||||
end
|
||||
-- finalize chunked transfer encoding
|
||||
c:send("0\r\n\r\n")
|
||||
-- close connection
|
||||
c:close()
|
||||
end
|
||||
--
|
||||
local make_res = function(conn)
|
||||
local res = {
|
||||
conn = conn,
|
||||
}
|
||||
-- return setmetatable(res, {
|
||||
-- send_header = send_header,
|
||||
-- send = send,
|
||||
-- finish = finish,
|
||||
-- })
|
||||
res.send_header = send_header
|
||||
res.send = send
|
||||
res.finish = finish
|
||||
return res
|
||||
end
|
||||
|
||||
tmr.wdclr()
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
-- HTTP parser
|
||||
------------------------------------------------------------------------------
|
||||
local http_handler = function(handler)
|
||||
return function(conn)
|
||||
local req, res
|
||||
local buf = ""
|
||||
local method, url
|
||||
local ondisconnect = function(conn)
|
||||
collectgarbage("collect")
|
||||
end
|
||||
-- header parser
|
||||
local cnt_len = 0
|
||||
local onheader = function(conn, k, v)
|
||||
-- TODO: look for Content-Type: header
|
||||
-- to help parse body
|
||||
-- parse content length to know body length
|
||||
if k == "content-length" then
|
||||
cnt_len = tonumber(v)
|
||||
end
|
||||
if k == "expect" and v == "100-continue" then
|
||||
conn:send("HTTP/1.1 100 Continue\r\n")
|
||||
end
|
||||
-- delegate to request object
|
||||
if req and req.onheader then
|
||||
req:onheader(k, v)
|
||||
end
|
||||
end
|
||||
-- body data handler
|
||||
local body_len = 0
|
||||
local ondata = function(conn, chunk)
|
||||
-- NB: do not reset node in case of lengthy requests
|
||||
tmr.wdclr()
|
||||
-- feed request data to request handler
|
||||
if not req or not req.ondata then return end
|
||||
req:ondata(chunk)
|
||||
-- NB: once length of seen chunks equals Content-Length:
|
||||
-- onend(conn) is called
|
||||
body_len = body_len + #chunk
|
||||
-- print("-B", #chunk, body_len, cnt_len, node.heap())
|
||||
if body_len >= cnt_len then
|
||||
req:ondata()
|
||||
end
|
||||
end
|
||||
local onreceive = function(conn, chunk)
|
||||
-- merge chunks in buffer
|
||||
if buf then
|
||||
buf = buf .. chunk
|
||||
else
|
||||
buf = chunk
|
||||
end
|
||||
-- consume buffer line by line
|
||||
while #buf > 0 do
|
||||
-- extract line
|
||||
local e = buf:find("\r\n", 1, true)
|
||||
if not e then break end
|
||||
local line = buf:sub(1, e - 1)
|
||||
buf = buf:sub(e + 2)
|
||||
-- method, url?
|
||||
if not method then
|
||||
local i
|
||||
-- NB: just version 1.1 assumed
|
||||
_, i, method, url = line:find("^([A-Z]+) (.-) HTTP/1.1$")
|
||||
if method then
|
||||
-- make request and response objects
|
||||
req = make_req(conn, method, url)
|
||||
res = make_res(conn)
|
||||
end
|
||||
-- header line?
|
||||
elseif #line > 0 then
|
||||
-- parse header
|
||||
local _, _, k, v = line:find("^([%w-]+):%s*(.+)")
|
||||
-- header seems ok?
|
||||
if k then
|
||||
k = k:lower()
|
||||
onheader(conn, k, v)
|
||||
end
|
||||
-- headers end
|
||||
else
|
||||
-- spawn request handler
|
||||
-- NB: do not reset in case of lengthy requests
|
||||
tmr.wdclr()
|
||||
handler(req, res)
|
||||
tmr.wdclr()
|
||||
-- NB: we feed the rest of the buffer as starting chunk of body
|
||||
ondata(conn, buf)
|
||||
-- buffer no longer needed
|
||||
buf = nil
|
||||
-- NB: we explicitly reassign receive handler so that
|
||||
-- next received chunks go directly to body handler
|
||||
conn:on("receive", ondata)
|
||||
-- parser done
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
conn:on("receive", onreceive)
|
||||
conn:on("disconnection", ondisconnect)
|
||||
end
|
||||
end
|
||||
|
||||
tmr.wdclr()
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
-- HTTP server
|
||||
------------------------------------------------------------------------------
|
||||
local srv
|
||||
local createServer = function(port, handler)
|
||||
-- NB: only one server at a time
|
||||
if srv then srv:close() end
|
||||
srv = net.createServer(net.TCP, 15)
|
||||
-- listen
|
||||
srv:listen(port, http_handler(handler))
|
||||
return srv
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
-- HTTP server methods
|
||||
------------------------------------------------------------------------------
|
||||
http = {
|
||||
createServer = createServer,
|
||||
}
|
||||
end
|
||||
|
||||
return http
|
Loading…
Reference in New Issue