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; |