-
Notifications
You must be signed in to change notification settings - Fork 16
/
http.lua
212 lines (206 loc) · 6.47 KB
/
http.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
------------------------------------------------------------------------------
-- HTTP server module
--
-- LICENSE: 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
------------------------------------------------------------------------------
-- 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
------------------------------------------------------------------------------
-- 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