Comparison

net/http/server.lua @ 4631:fc5d3b053454

net.http.{server|codes|parser}: Initial commit.
author Waqas Hussain <waqas20@gmail.com>
date Sun, 08 Apr 2012 04:09:33 +0500
child 4633:92e1a538f8b0
comparison
equal deleted inserted replaced
4630:9502c0224caf 4631:fc5d3b053454
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 debug = debug;
13 local tostring = tostring;
14 local codes = require "net.http.codes";
15 local _G = _G;
16
17 local _M = {};
18
19 local sessions = {};
20 local handlers = {};
21
22 local listener = {};
23
24 local handle_request;
25 local _1, _2, _3;
26 local function _handle_request() return handle_request(_1, _2, _3); end
27 local function _traceback_handler(err) log("error", "Traceback[http]: %s: %s", tostring(err), debug.traceback()); end
28
29 function listener.onconnect(conn)
30 local secure = conn:ssl() and true or nil;
31 local pending = {};
32 local waiting = false;
33 local function process_next(last_response)
34 --if waiting then log("debug", "can't process_next, waiting"); return; end
35 if sessions[conn] and #pending > 0 then
36 local request = t_remove(pending);
37 --log("debug", "process_next: %s", request.path);
38 waiting = true;
39 --handle_request(conn, request, process_next);
40 _1, _2, _3 = conn, request, process_next;
41 if not xpcall(_handle_request, _traceback_handler) then
42 conn:write("HTTP/1.0 503 Internal Server Error\r\n\r\nAn error occured during the processing of this request.");
43 conn:close();
44 end
45 else
46 --log("debug", "ready for more");
47 waiting = false;
48 end
49 end
50 local function success_cb(request)
51 --log("debug", "success_cb: %s", request.path);
52 request.secure = secure;
53 t_insert(pending, request);
54 if not waiting then
55 process_next();
56 end
57 end
58 local function error_cb(err)
59 log("debug", "error_cb: %s", err or "<nil>");
60 -- FIXME don't close immediately, wait until we process current stuff
61 -- FIXME if err, send off a bad-request response
62 sessions[conn] = nil;
63 conn:close();
64 end
65 sessions[conn] = parser_new(success_cb, error_cb);
66 end
67
68 function listener.ondisconnect(conn)
69 sessions[conn] = nil;
70 end
71
72 function listener.onincoming(conn, data)
73 sessions[conn]:feed(data);
74 end
75
76 local headerfix = setmetatable({}, {
77 __index = function(t, k)
78 local v = "\r\n"..k:gsub("_", "-"):gsub("%f[%w].", s_upper)..": ";
79 t[k] = v;
80 return v;
81 end
82 });
83
84 function _M.hijack_response(response, listener)
85 error("TODO");
86 end
87 function handle_request(conn, request, finish_cb)
88 --log("debug", "handler: %s", request.path);
89 local headers = {};
90 for k,v in pairs(request.headers) do headers[k:gsub("-", "_")] = v; end
91 request.headers = headers;
92 request.conn = conn;
93
94 local date_header = os_date('!%a, %d %b %Y %H:%M:%S GMT'); -- FIXME use
95 local conn_header = request.headers.connection;
96 local keep_alive = conn_header == "Keep-Alive" or (request.httpversion == "1.1" and conn_header ~= "close");
97
98 local response = {
99 request = request;
100 status_code = 200;
101 headers = { date = date_header, connection = (keep_alive and "Keep-Alive" or "close") };
102 conn = conn;
103 send = _M.send_response;
104 finish_cb = finish_cb;
105 };
106
107 if not request.headers.host then
108 response.status_code = 400;
109 response.headers.content_type = "text/html";
110 response:send("<html><head>400 Bad Request</head><body>400 Bad Request: No Host header.</body></html>");
111 else
112 -- TODO call handler
113 --response.headers.content_type = "text/plain";
114 --response:send("host="..(request.headers.host or "").."\npath="..request.path.."\n"..(request.body or ""));
115 local host = request.headers.host;
116 if host then
117 host = host:match("[^:]*"):lower();
118 local event = request.method.." "..host..request.path:match("[^?]*");
119 local payload = { request = request, response = response };
120 --[[repeat
121 if events.fire_event(event, payload) ~= nil then return; end
122 event = (event:sub(-1) == "/") and event:sub(1, -1) or event:gsub("[^/]*$", "");
123 if event:sub(-1) == "/" then
124 event = event:sub(1, -1);
125 else
126 event = event:gsub("[^/]*$", "");
127 end
128 until not event:find("/", 1, true);]]
129 --log("debug", "Event: %s", event);
130 if events.fire_event(event, payload) ~= nil then return; end
131 -- TODO try adding/stripping / at the end, but this needs to work via an HTTP redirect
132 end
133
134 -- if handler not called, fallback to legacy httpserver handlers
135 _M.legacy_handler(request, response);
136 end
137 end
138 function _M.send_response(response, body)
139 local status_line = "HTTP/"..response.request.httpversion.." "..(response.status or codes[response.status_code]);
140 local headers = response.headers;
141 body = body or "";
142 headers.content_length = #body;
143
144 local output = { status_line };
145 for k,v in pairs(headers) do
146 t_insert(output, headerfix[k]..v);
147 end
148 t_insert(output, "\r\n\r\n");
149 t_insert(output, body);
150
151 response.conn:write(t_concat(output));
152 if headers.connection == "Keep-Alive" then
153 response:finish_cb();
154 else
155 response.conn:close();
156 end
157 end
158 function _M.legacy_handler(request, response)
159 log("debug", "Invoking legacy handler");
160 local base = request.path:match("^/([^/?]+)");
161 local legacy_server = _G.httpserver and _G.httpserver.new.http_servers[5280];
162 local handler = legacy_server and legacy_server.handlers[base];
163 if not handler then handler = _G.httpserver and _G.httpserver.set_default_handler.default_handler; end
164 if handler then
165 -- add legacy properties to request object
166 request.url = { path = request.path };
167 request.handler = response.conn;
168 request.id = tostring{}:match("%x+$");
169 local headers = {};
170 for k,v in pairs(request.headers) do
171 headers[k:gsub("_", "-")] = v;
172 end
173 request.headers = headers;
174 function request:send(resp)
175 if self.destroyed then return; end
176 if resp.body or resp.headers then
177 if resp.headers then
178 for k,v in pairs(resp.headers) do response.headers[k] = v; end
179 end
180 response:send(resp.body)
181 else
182 response:send(resp)
183 end
184 self.sent = true;
185 self:destroy();
186 end
187 function request:destroy()
188 if self.destroyed then return; end
189 if not self.sent then return self:send(""); end
190 self.destroyed = true;
191 if self.on_destroy then
192 log("debug", "Request has destroy callback");
193 self:on_destroy();
194 else
195 log("debug", "Request has no destroy callback");
196 end
197 end
198 local r = handler(request.method, request.body, request);
199 if r ~= true then
200 request:send(r);
201 end
202 else
203 log("debug", "No handler found");
204 response.status_code = 404;
205 response.headers.content_type = "text/html";
206 response:send("<html><head>404 Not Found</head><body>404 Not Found: No such page.</body></html>");
207 end
208 end
209
210 function _M.add_handler(event, handler, priority)
211 events.add_handler(event, handler, priority);
212 end
213 function _M.remove_handler(event, handler)
214 events.remove_handler(event, handler);
215 end
216
217 function _M.listen_on(port, interface, ssl)
218 addserver(interface or "*", port, listener, "*a", ssl);
219 end
220
221 _M.listener = listener;
222 _M.codes = codes;
223 return _M;