|
1 |
|
2 local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat; |
|
3 local parser_new = require "net.http.parser".new; |
|
4 local events = require "util.events".new(); |
|
5 local addserver = require "net.server".addserver; |
|
6 local log = require "util.logger".init("http.server"); |
|
7 local os_date = os.date; |
|
8 local pairs = pairs; |
|
9 local s_upper = string.upper; |
|
10 local setmetatable = setmetatable; |
|
11 local xpcall = xpcall; |
|
12 local traceback = debug.traceback; |
|
13 local tostring = tostring; |
|
14 local cache = require "util.cache"; |
|
15 local codes = require "net.http.codes"; |
|
16 local blocksize = 2^16; |
|
17 |
|
18 local _M = {}; |
|
19 |
|
20 local sessions = {}; |
|
21 local incomplete = {}; |
|
22 local listener = {}; |
|
23 local hosts = {}; |
|
24 local default_host; |
|
25 local options = {}; |
|
26 |
|
27 local function is_wildcard_event(event) |
|
28 return event:sub(-2, -1) == "/*"; |
|
29 end |
|
30 local function is_wildcard_match(wildcard_event, event) |
|
31 return wildcard_event:sub(1, -2) == event:sub(1, #wildcard_event-1); |
|
32 end |
|
33 |
|
34 local _handlers = events._handlers; |
|
35 local recent_wildcard_events = cache.new(10000, function (key, value) -- luacheck: ignore 212/value |
|
36 rawset(_handlers, key, nil); |
|
37 end); |
|
38 |
|
39 local event_map = events._event_map; |
|
40 setmetatable(events._handlers, { |
|
41 -- Called when firing an event that doesn't exist (but may match a wildcard handler) |
|
42 __index = function (handlers, curr_event) |
|
43 if is_wildcard_event(curr_event) then return; end -- Wildcard events cannot be fired |
|
44 -- Find all handlers that could match this event, sort them |
|
45 -- and then put the array into handlers[curr_event] (and return it) |
|
46 local matching_handlers_set = {}; |
|
47 local handlers_array = {}; |
|
48 for event, handlers_set in pairs(event_map) do |
|
49 if event == curr_event or |
|
50 is_wildcard_event(event) and is_wildcard_match(event, curr_event) then |
|
51 for handler, priority in pairs(handlers_set) do |
|
52 matching_handlers_set[handler] = { (select(2, event:gsub("/", "%1"))), is_wildcard_event(event) and 0 or 1, priority }; |
|
53 table.insert(handlers_array, handler); |
|
54 end |
|
55 end |
|
56 end |
|
57 if #handlers_array > 0 then |
|
58 table.sort(handlers_array, function(b, a) |
|
59 local a_score, b_score = matching_handlers_set[a], matching_handlers_set[b]; |
|
60 for i = 1, #a_score do |
|
61 if a_score[i] ~= b_score[i] then -- If equal, compare next score value |
|
62 return a_score[i] < b_score[i]; |
|
63 end |
|
64 end |
|
65 return false; |
|
66 end); |
|
67 else |
|
68 handlers_array = false; |
|
69 end |
|
70 rawset(handlers, curr_event, handlers_array); |
|
71 if not event_map[curr_event] then -- Only wildcard handlers match, if any |
|
72 recent_wildcard_events:set(curr_event, true); |
|
73 end |
|
74 return handlers_array; |
|
75 end; |
|
76 __newindex = function (handlers, curr_event, handlers_array) |
|
77 if handlers_array == nil |
|
78 and is_wildcard_event(curr_event) then |
|
79 -- Invalidate the indexes of all matching events |
|
80 for event in pairs(handlers) do |
|
81 if is_wildcard_match(curr_event, event) then |
|
82 handlers[event] = nil; |
|
83 end |
|
84 end |
|
85 end |
|
86 rawset(handlers, curr_event, handlers_array); |
|
87 end; |
|
88 }); |
|
89 |
|
90 local handle_request; |
|
91 local _1, _2, _3; |
|
92 local function _handle_request() return handle_request(_1, _2, _3); end |
|
93 |
|
94 local last_err; |
|
95 local function _traceback_handler(err) last_err = err; log("error", "Traceback[httpserver]: %s", traceback(tostring(err), 2)); end |
|
96 events.add_handler("http-error", function (error) |
|
97 return "Error processing request: "..codes[error.code]..". Check your error log for more information."; |
|
98 end, -1); |
|
99 |
|
100 function listener.onconnect(conn) |
|
101 local secure = conn:ssl() and true or nil; |
|
102 local pending = {}; |
|
103 local waiting = false; |
|
104 local function process_next() |
|
105 if waiting then return; end -- log("debug", "can't process_next, waiting"); |
|
106 waiting = true; |
|
107 while sessions[conn] and #pending > 0 do |
|
108 local request = t_remove(pending); |
|
109 --log("debug", "process_next: %s", request.path); |
|
110 --handle_request(conn, request, process_next); |
|
111 _1, _2, _3 = conn, request, process_next; |
|
112 if not xpcall(_handle_request, _traceback_handler) then |
|
113 conn:write("HTTP/1.0 500 Internal Server Error\r\n\r\n"..events.fire_event("http-error", { code = 500, private_message = last_err })); |
|
114 conn:close(); |
|
115 end |
|
116 end |
|
117 --log("debug", "ready for more"); |
|
118 waiting = false; |
|
119 end |
|
120 local function success_cb(request) |
|
121 --log("debug", "success_cb: %s", request.path); |
|
122 if waiting then |
|
123 log("error", "http connection handler is not reentrant: %s", request.path); |
|
124 assert(false, "http connection handler is not reentrant"); |
|
125 end |
|
126 request.secure = secure; |
|
127 t_insert(pending, request); |
|
128 process_next(); |
|
129 end |
|
130 local function error_cb(err) |
|
131 log("debug", "error_cb: %s", err or "<nil>"); |
|
132 -- FIXME don't close immediately, wait until we process current stuff |
|
133 -- FIXME if err, send off a bad-request response |
|
134 sessions[conn] = nil; |
|
135 conn:close(); |
|
136 end |
|
137 local function options_cb() |
|
138 return options; |
|
139 end |
|
140 sessions[conn] = parser_new(success_cb, error_cb, "server", options_cb); |
|
141 end |
|
142 |
|
143 function listener.ondisconnect(conn) |
|
144 local open_response = conn._http_open_response; |
|
145 if open_response and open_response.on_destroy then |
|
146 open_response.finished = true; |
|
147 open_response:on_destroy(); |
|
148 end |
|
149 incomplete[conn] = nil; |
|
150 sessions[conn] = nil; |
|
151 end |
|
152 |
|
153 function listener.ondetach(conn) |
|
154 sessions[conn] = nil; |
|
155 incomplete[conn] = nil; |
|
156 end |
|
157 |
|
158 function listener.onincoming(conn, data) |
|
159 sessions[conn]:feed(data); |
|
160 end |
|
161 |
|
162 function listener.ondrain(conn) |
|
163 local response = incomplete[conn]; |
|
164 if response and response._send_more then |
|
165 response._send_more(); |
|
166 end |
|
167 end |
|
168 |
|
169 local headerfix = setmetatable({}, { |
|
170 __index = function(t, k) |
|
171 local v = "\r\n"..k:gsub("_", "-"):gsub("%f[%w].", s_upper)..": "; |
|
172 t[k] = v; |
|
173 return v; |
|
174 end |
|
175 }); |
|
176 |
|
177 function _M.hijack_response(response, listener) -- luacheck: ignore |
|
178 error("TODO"); |
|
179 end |
|
180 function handle_request(conn, request, finish_cb) |
|
181 --log("debug", "handler: %s", request.path); |
|
182 local headers = {}; |
|
183 for k,v in pairs(request.headers) do headers[k:gsub("-", "_")] = v; end |
|
184 request.headers = headers; |
|
185 request.conn = conn; |
|
186 |
|
187 local date_header = os_date('!%a, %d %b %Y %H:%M:%S GMT'); -- FIXME use |
|
188 local conn_header = request.headers.connection; |
|
189 conn_header = conn_header and ","..conn_header:gsub("[ \t]", ""):lower().."," or "" |
|
190 local httpversion = request.httpversion |
|
191 local persistent = conn_header:find(",keep-alive,", 1, true) |
|
192 or (httpversion == "1.1" and not conn_header:find(",close,", 1, true)); |
|
193 |
|
194 local response_conn_header; |
|
195 if persistent then |
|
196 response_conn_header = "Keep-Alive"; |
|
197 else |
|
198 response_conn_header = httpversion == "1.1" and "close" or nil |
|
199 end |
|
200 |
|
201 local response = { |
|
202 request = request; |
|
203 status_code = 200; |
|
204 headers = { date = date_header, connection = response_conn_header }; |
|
205 persistent = persistent; |
|
206 conn = conn; |
|
207 send = _M.send_response; |
|
208 send_file = _M.send_file; |
|
209 done = _M.finish_response; |
|
210 finish_cb = finish_cb; |
|
211 }; |
|
212 conn._http_open_response = response; |
|
213 |
|
214 local host = (request.headers.host or ""):match("[^:]+"); |
|
215 |
|
216 -- Some sanity checking |
|
217 local err_code, err; |
|
218 if not request.path then |
|
219 err_code, err = 400, "Invalid path"; |
|
220 elseif not hosts[host] then |
|
221 if hosts[default_host] then |
|
222 host = default_host; |
|
223 elseif host then |
|
224 err_code, err = 404, "Unknown host: "..host; |
|
225 else |
|
226 err_code, err = 400, "Missing or invalid 'Host' header"; |
|
227 end |
|
228 end |
|
229 |
|
230 if err then |
|
231 response.status_code = err_code; |
|
232 response:send(events.fire_event("http-error", { code = err_code, message = err, response = response })); |
|
233 return; |
|
234 end |
|
235 |
|
236 local event = request.method.." "..host..request.path:match("[^?]*"); |
|
237 local payload = { request = request, response = response }; |
|
238 log("debug", "Firing event: %s", event); |
|
239 local result = events.fire_event(event, payload); |
|
240 if result ~= nil then |
|
241 if result ~= true then |
|
242 local body; |
|
243 local result_type = type(result); |
|
244 if result_type == "number" then |
|
245 response.status_code = result; |
|
246 if result >= 400 then |
|
247 payload.code = result; |
|
248 body = events.fire_event("http-error", payload); |
|
249 end |
|
250 elseif result_type == "string" then |
|
251 body = result; |
|
252 elseif result_type == "table" then |
|
253 for k, v in pairs(result) do |
|
254 if k ~= "headers" then |
|
255 response[k] = v; |
|
256 else |
|
257 for header_name, header_value in pairs(v) do |
|
258 response.headers[header_name] = header_value; |
|
259 end |
|
260 end |
|
261 end |
|
262 end |
|
263 response:send(body); |
|
264 end |
|
265 return; |
|
266 end |
|
267 |
|
268 -- if handler not called, return 404 |
|
269 response.status_code = 404; |
|
270 payload.code = 404; |
|
271 response:send(events.fire_event("http-error", payload)); |
|
272 end |
|
273 local function prepare_header(response) |
|
274 local status_line = "HTTP/"..response.request.httpversion.." "..(response.status or codes[response.status_code]); |
|
275 local headers = response.headers; |
|
276 local output = { status_line }; |
|
277 for k,v in pairs(headers) do |
|
278 t_insert(output, headerfix[k]..v); |
|
279 end |
|
280 t_insert(output, "\r\n\r\n"); |
|
281 return output; |
|
282 end |
|
283 _M.prepare_header = prepare_header; |
|
284 function _M.send_response(response, body) |
|
285 if response.finished then return; end |
|
286 body = body or response.body or ""; |
|
287 response.headers.content_length = #body; |
|
288 local output = prepare_header(response); |
|
289 t_insert(output, body); |
|
290 response.conn:write(t_concat(output)); |
|
291 response:done(); |
|
292 end |
|
293 function _M.send_file(response, f) |
|
294 if response.finished then return; end |
|
295 local chunked = not response.headers.content_length; |
|
296 if chunked then response.headers.transfer_encoding = "chunked"; end |
|
297 incomplete[response.conn] = response; |
|
298 response._send_more = function () |
|
299 if response.finished then |
|
300 incomplete[response.conn] = nil; |
|
301 return; |
|
302 end |
|
303 local chunk = f:read(blocksize); |
|
304 if chunk then |
|
305 if chunked then |
|
306 chunk = ("%x\r\n%s\r\n"):format(#chunk, chunk); |
|
307 end |
|
308 -- io.write("."); io.flush(); |
|
309 response.conn:write(chunk); |
|
310 else |
|
311 if chunked then |
|
312 response.conn:write("0\r\n\r\n"); |
|
313 end |
|
314 -- io.write("\n"); |
|
315 if f.close then f:close(); end |
|
316 incomplete[response.conn] = nil; |
|
317 return response:done(); |
|
318 end |
|
319 end |
|
320 response.conn:write(t_concat(prepare_header(response))); |
|
321 return true; |
|
322 end |
|
323 function _M.finish_response(response) |
|
324 if response.finished then return; end |
|
325 response.finished = true; |
|
326 response.conn._http_open_response = nil; |
|
327 if response.on_destroy then |
|
328 response:on_destroy(); |
|
329 response.on_destroy = nil; |
|
330 end |
|
331 if response.persistent then |
|
332 response:finish_cb(); |
|
333 else |
|
334 response.conn:close(); |
|
335 end |
|
336 end |
|
337 function _M.add_handler(event, handler, priority) |
|
338 events.add_handler(event, handler, priority); |
|
339 end |
|
340 function _M.remove_handler(event, handler) |
|
341 events.remove_handler(event, handler); |
|
342 end |
|
343 |
|
344 function _M.listen_on(port, interface, ssl) |
|
345 return addserver(interface or "*", port, listener, "*a", ssl); |
|
346 end |
|
347 function _M.add_host(host) |
|
348 hosts[host] = true; |
|
349 end |
|
350 function _M.remove_host(host) |
|
351 hosts[host] = nil; |
|
352 end |
|
353 function _M.set_default_host(host) |
|
354 default_host = host; |
|
355 end |
|
356 function _M.fire_event(event, ...) |
|
357 return events.fire_event(event, ...); |
|
358 end |
|
359 function _M.set_option(name, value) |
|
360 options[name] = value; |
|
361 end |
|
362 |
|
363 _M.listener = listener; |
|
364 _M.codes = codes; |
|
365 _M._events = events; |
|
366 return _M; |