Software /
code /
prosody
Comparison
net/httpserver.lua @ 634:1af93ea23f96
Adding initial net.httpserver (lots of work to do on it)
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Mon, 22 Dec 2008 22:12:11 +0000 |
child | 697:8ddc85fa7602 |
comparison
equal
deleted
inserted
replaced
633:fe1e01a06729 | 634:1af93ea23f96 |
---|---|
1 | |
2 local socket = require "socket" | |
3 local server = require "net.server" | |
4 local url_parse = require "socket.url".parse; | |
5 | |
6 local connlisteners_start = require "net.connlisteners".start; | |
7 local connlisteners_get = require "net.connlisteners".get; | |
8 local listener; | |
9 | |
10 local t_insert, t_concat = table.insert, table.concat; | |
11 local s_match, s_gmatch = string.match, string.gmatch; | |
12 local tonumber, tostring, pairs = tonumber, tostring, pairs; | |
13 | |
14 local urlcodes = setmetatable({}, { __index = function (t, k) t[k] = char(tonumber("0x"..k)); return t[k]; end }); | |
15 local urlencode = function (s) return s and (s:gsub("%W", function (c) return string.format("%%%x", c:byte()); end)); end | |
16 | |
17 local log = require "util.logger".init("httpserver"); | |
18 | |
19 local http_servers = {}; | |
20 | |
21 module "httpserver" | |
22 | |
23 local default_handler; | |
24 | |
25 local function expectbody(reqt) | |
26 return reqt.method == "POST"; | |
27 end | |
28 | |
29 local function send_response(request, response) | |
30 -- Write status line | |
31 local resp; | |
32 if response.body then | |
33 log("debug", "Sending response to %s: %s", request.id, response.body); | |
34 resp = { "HTTP/1.0 ", response.status or "200 OK", "\r\n"}; | |
35 local h = response.headers; | |
36 if h then | |
37 for k, v in pairs(h) do | |
38 t_insert(resp, k); | |
39 t_insert(resp, ": "); | |
40 t_insert(resp, v); | |
41 t_insert(resp, "\r\n"); | |
42 end | |
43 end | |
44 if response.body and not (h and h["Content-Length"]) then | |
45 t_insert(resp, "Content-Length: "); | |
46 t_insert(resp, #response.body); | |
47 t_insert(resp, "\r\n"); | |
48 end | |
49 t_insert(resp, "\r\n"); | |
50 | |
51 if response.body and request.method ~= "HEAD" then | |
52 t_insert(resp, response.body); | |
53 end | |
54 else | |
55 -- Response we have is just a string (the body) | |
56 log("debug", "Sending response to %s: %s", request.id, response); | |
57 | |
58 resp = { "HTTP/1.0 200 OK\r\n" }; | |
59 t_insert(resp, "Connection: close\r\n"); | |
60 t_insert(resp, "Content-Length: "); | |
61 t_insert(resp, #response); | |
62 t_insert(resp, "\r\n\r\n"); | |
63 | |
64 t_insert(resp, response); | |
65 end | |
66 request.write(t_concat(resp)); | |
67 if not request.stayopen then | |
68 request:destroy(); | |
69 end | |
70 end | |
71 | |
72 local function call_callback(request, err) | |
73 if request.handled then return; end | |
74 request.handled = true; | |
75 local callback = request.callback; | |
76 if not callback and request.path then | |
77 local path = request.url.path; | |
78 local base = path:match("^/([^/?]+)"); | |
79 if not base then | |
80 base = path:match("^http://[^/?]+/([^/?]+)"); | |
81 end | |
82 | |
83 callback = (request.server and request.server.handlers[base]) or default_handler; | |
84 if callback == default_handler then | |
85 log("debug", "Default callback for this request (base: "..tostring(base)..")") | |
86 end | |
87 end | |
88 if callback then | |
89 if err then | |
90 log("debug", "Request error: "..err); | |
91 if not callback(nil, err, request) then | |
92 destroy_request(request); | |
93 end | |
94 return; | |
95 end | |
96 | |
97 local response = callback(request.method, request.body and t_concat(request.body), request); | |
98 if response then | |
99 if response == true then | |
100 -- Keep connection open, we will reply later | |
101 log("warn", "Request %s left open, on_destroy is %s", request.id, tostring(request.on_destroy)); | |
102 else | |
103 -- Assume response | |
104 send_response(request, response); | |
105 destroy_request(request); | |
106 end | |
107 else | |
108 log("debug", "Request handler provided no response, destroying request..."); | |
109 -- No response, close connection | |
110 destroy_request(request); | |
111 end | |
112 end | |
113 end | |
114 | |
115 local function request_reader(request, data, startpos) | |
116 if not data then | |
117 if request.body then | |
118 call_callback(request); | |
119 else | |
120 -- Error.. connection was closed prematurely | |
121 call_callback(request, "connection-closed"); | |
122 end | |
123 -- Here we force a destroy... the connection is gone, so we can't reply later | |
124 destroy_request(request); | |
125 return; | |
126 end | |
127 if request.state == "body" then | |
128 log("debug", "Reading body...") | |
129 if not request.body then request.body = {}; request.havebodylength, request.bodylength = 0, tonumber(request.responseheaders["content-length"]); end | |
130 if startpos then | |
131 data = data:sub(startpos, -1) | |
132 end | |
133 t_insert(request.body, data); | |
134 if request.bodylength then | |
135 request.havebodylength = request.havebodylength + #data; | |
136 if request.havebodylength >= request.bodylength then | |
137 -- We have the body | |
138 call_callback(request); | |
139 end | |
140 end | |
141 elseif request.state == "headers" then | |
142 log("debug", "Reading headers...") | |
143 local pos = startpos; | |
144 local headers = request.responseheaders or {}; | |
145 for line in data:gmatch("(.-)\r\n") do | |
146 startpos = (startpos or 1) + #line + 2; | |
147 local k, v = line:match("(%S+): (.+)"); | |
148 if k and v then | |
149 headers[k:lower()] = v; | |
150 -- log("debug", "Header: "..k:lower().." = "..v); | |
151 elseif #line == 0 then | |
152 request.responseheaders = headers; | |
153 break; | |
154 else | |
155 log("debug", "Unhandled header line: "..line); | |
156 end | |
157 end | |
158 | |
159 if not expectbody(request) then | |
160 call_callback(request); | |
161 return; | |
162 end | |
163 | |
164 -- Reached the end of the headers | |
165 request.state = "body"; | |
166 if #data > startpos then | |
167 return request_reader(request, data:sub(startpos, -1)); | |
168 end | |
169 elseif request.state == "request" then | |
170 log("debug", "Reading request line...") | |
171 local method, path, http, linelen = data:match("^(%S+) (%S+) HTTP/(%S+)\r\n()", startpos); | |
172 if not method then | |
173 return call_callback(request, "invalid-status-line"); | |
174 end | |
175 | |
176 request.method, request.path, request.httpversion = method, path, http; | |
177 | |
178 request.url = url_parse(request.path); | |
179 | |
180 log("debug", method.." request for "..tostring(request.path) .. " on port "..request.handler.serverport()); | |
181 | |
182 if request.onlystatus then | |
183 if not call_callback(request) then | |
184 return; | |
185 end | |
186 end | |
187 | |
188 request.state = "headers"; | |
189 | |
190 if #data > linelen then | |
191 return request_reader(request, data:sub(linelen, -1)); | |
192 end | |
193 end | |
194 end | |
195 | |
196 -- The default handler for requests | |
197 default_handler = function (method, body, request) | |
198 log("debug", method.." request for "..tostring(request.path) .. " on port "..request.handler.serverport()); | |
199 return { status = "404 Not Found", | |
200 headers = { ["Content-Type"] = "text/html" }, | |
201 body = "<html><head><title>Page Not Found</title></head><body>Not here :(</body></html>" }; | |
202 end | |
203 | |
204 | |
205 function new_request(handler) | |
206 return { handler = handler, conn = handler.socket, | |
207 write = handler.write, state = "request", | |
208 server = http_servers[handler.serverport()], | |
209 send = send_response, | |
210 destroy = destroy_request, | |
211 id = tostring{}:match("%x+$") | |
212 }; | |
213 end | |
214 | |
215 function destroy_request(request) | |
216 log("debug", "Destroying request %s", request.id); | |
217 listener = listener or connlisteners_get("httpserver"); | |
218 if not request.destroyed then | |
219 request.destroyed = true; | |
220 if request.on_destroy then | |
221 log("debug", "Request has destroy callback"); | |
222 request.on_destroy(request); | |
223 else | |
224 log("debug", "Request has no destroy callback"); | |
225 end | |
226 request.handler.close() | |
227 if request.conn then | |
228 listener.disconnect(request.conn, "closed"); | |
229 end | |
230 end | |
231 end | |
232 | |
233 function new(params) | |
234 local http_server = http_servers[params.port]; | |
235 if not http_server then | |
236 http_server = { handlers = {} }; | |
237 http_servers[params.port] = http_server; | |
238 -- We weren't already listening on this port, so start now | |
239 connlisteners_start("httpserver", params); | |
240 end | |
241 if params.base then | |
242 http_server.handlers[params.base] = params.handler; | |
243 end | |
244 end | |
245 | |
246 _M.request_reader = request_reader; | |
247 _M.send_response = send_response; | |
248 _M.urlencode = urlencode; | |
249 | |
250 return _M; |