| 1 | -- Prosody IM |
| 2 | -- Copyright (C) 2008-2010 Matthew Wild |
| 3 | -- Copyright (C) 2008-2010 Waqas Hussain |
| 4 | -- |
| 5 | -- This project is MIT/X11 licensed. Please see the |
| 6 | -- COPYING file in the source package for more information. |
| 7 | -- |
| 8 | |
| 9 | |
| 10 | local socket = require "socket" |
| 11 | local server = require "net.server" |
| 12 | local url_parse = require "socket.url".parse; |
| 13 | local httpstream_new = require "util.httpstream".new; |
| 14 | |
| 15 | local connlisteners_start = require "net.connlisteners".start; |
| 16 | local connlisteners_get = require "net.connlisteners".get; |
| 17 | local listener; |
| 18 | |
| 19 | local t_insert, t_concat = table.insert, table.concat; |
| 20 | local s_match, s_gmatch = string.match, string.gmatch; |
| 21 | local tonumber, tostring, pairs, ipairs, type = tonumber, tostring, pairs, ipairs, type; |
| 22 | |
| 23 | local urlencode = function (s) return s and (s:gsub("%W", function (c) return string.format("%%%02x", c:byte()); end)); end |
| 24 | |
| 25 | local log = require "util.logger".init("httpserver"); |
| 26 | |
| 27 | local http_servers = {}; |
| 28 | |
| 29 | module "httpserver" |
| 30 | |
| 31 | local default_handler; |
| 32 | |
| 33 | local function expectbody(reqt) |
| 34 | return reqt.method == "POST"; |
| 35 | end |
| 36 | |
| 37 | local function send_response(request, response) |
| 38 | -- Write status line |
| 39 | local resp; |
| 40 | if response.body or response.headers then |
| 41 | local body = response.body and tostring(response.body); |
| 42 | log("debug", "Sending response to %s", request.id); |
| 43 | resp = { "HTTP/1.0 "..(response.status or "200 OK").."\r\n" }; |
| 44 | local h = response.headers; |
| 45 | if h then |
| 46 | for k, v in pairs(h) do |
| 47 | t_insert(resp, k..": "..v.."\r\n"); |
| 48 | end |
| 49 | end |
| 50 | if body and not (h and h["Content-Length"]) then |
| 51 | t_insert(resp, "Content-Length: "..#body.."\r\n"); |
| 52 | end |
| 53 | t_insert(resp, "\r\n"); |
| 54 | |
| 55 | if body and request.method ~= "HEAD" then |
| 56 | t_insert(resp, body); |
| 57 | end |
| 58 | request.write(t_concat(resp)); |
| 59 | else |
| 60 | -- Response we have is just a string (the body) |
| 61 | log("debug", "Sending 200 response to %s", request.id or "<none>"); |
| 62 | |
| 63 | local resp = "HTTP/1.0 200 OK\r\n" |
| 64 | .. "Connection: close\r\n" |
| 65 | .. "Content-Type: text/html\r\n" |
| 66 | .. "Content-Length: "..#response.."\r\n" |
| 67 | .. "\r\n" |
| 68 | .. response; |
| 69 | |
| 70 | request.write(resp); |
| 71 | end |
| 72 | if not request.stayopen then |
| 73 | request:destroy(); |
| 74 | end |
| 75 | end |
| 76 | |
| 77 | local function call_callback(request, err) |
| 78 | if request.handled then return; end |
| 79 | request.handled = true; |
| 80 | local callback = request.callback; |
| 81 | if not callback and request.path then |
| 82 | local path = request.url.path; |
| 83 | local base = path:match("^/([^/?]+)"); |
| 84 | if not base then |
| 85 | base = path:match("^http://[^/?]+/([^/?]+)"); |
| 86 | end |
| 87 | |
| 88 | callback = (request.server and request.server.handlers[base]) or default_handler; |
| 89 | end |
| 90 | if callback then |
| 91 | if err then |
| 92 | log("debug", "Request error: "..err); |
| 93 | if not callback(nil, err, request) then |
| 94 | destroy_request(request); |
| 95 | end |
| 96 | return; |
| 97 | end |
| 98 | |
| 99 | local response = callback(request.method, request.body and t_concat(request.body), request); |
| 100 | if response then |
| 101 | if response == true and not request.destroyed then |
| 102 | -- Keep connection open, we will reply later |
| 103 | log("debug", "Request %s left open, on_destroy is %s", request.id, tostring(request.on_destroy)); |
| 104 | elseif response ~= true then |
| 105 | -- Assume response |
| 106 | send_response(request, response); |
| 107 | destroy_request(request); |
| 108 | end |
| 109 | else |
| 110 | log("debug", "Request handler provided no response, destroying request..."); |
| 111 | -- No response, close connection |
| 112 | destroy_request(request); |
| 113 | end |
| 114 | end |
| 115 | end |
| 116 | |
| 117 | local function request_reader(request, data, startpos) |
| 118 | if not request.parser then |
| 119 | local function success_cb(r) |
| 120 | for k,v in pairs(r) do request[k] = v; end |
| 121 | request.url = url_parse(request.path); |
| 122 | request.body = { request.body }; |
| 123 | call_callback(request); |
| 124 | end |
| 125 | local function error_cb(r) |
| 126 | call_callback(request, r or "connection-closed"); |
| 127 | destroy_request(request); |
| 128 | end |
| 129 | request.parser = httpstream_new(success_cb, error_cb); |
| 130 | end |
| 131 | request.parser:feed(data); |
| 132 | end |
| 133 | |
| 134 | -- The default handler for requests |
| 135 | default_handler = function (method, body, request) |
| 136 | log("debug", method.." request for "..tostring(request.path) .. " on port "..request.handler:serverport()); |
| 137 | return { status = "404 Not Found", |
| 138 | headers = { ["Content-Type"] = "text/html" }, |
| 139 | body = "<html><head><title>Page Not Found</title></head><body>Not here :(</body></html>" }; |
| 140 | end |
| 141 | |
| 142 | |
| 143 | function new_request(handler) |
| 144 | return { handler = handler, conn = handler, |
| 145 | write = function (...) return handler:write(...); end, state = "request", |
| 146 | server = http_servers[handler:serverport()], |
| 147 | send = send_response, |
| 148 | destroy = destroy_request, |
| 149 | id = tostring{}:match("%x+$") |
| 150 | }; |
| 151 | end |
| 152 | |
| 153 | function destroy_request(request) |
| 154 | log("debug", "Destroying request %s", request.id); |
| 155 | listener = listener or connlisteners_get("httpserver"); |
| 156 | if not request.destroyed then |
| 157 | request.destroyed = true; |
| 158 | if request.on_destroy then |
| 159 | log("debug", "Request has destroy callback"); |
| 160 | request.on_destroy(request); |
| 161 | else |
| 162 | log("debug", "Request has no destroy callback"); |
| 163 | end |
| 164 | request.handler:close() |
| 165 | if request.conn then |
| 166 | listener.ondisconnect(request.conn, "closed"); |
| 167 | end |
| 168 | end |
| 169 | end |
| 170 | |
| 171 | function new(params) |
| 172 | local http_server = http_servers[params.port]; |
| 173 | if not http_server then |
| 174 | http_server = { handlers = {} }; |
| 175 | http_servers[params.port] = http_server; |
| 176 | -- We weren't already listening on this port, so start now |
| 177 | connlisteners_start("httpserver", params); |
| 178 | end |
| 179 | if params.base then |
| 180 | http_server.handlers[params.base] = params.handler; |
| 181 | end |
| 182 | end |
| 183 | |
| 184 | function set_default_handler(handler) |
| 185 | default_handler = handler; |
| 186 | end |
| 187 | |
| 188 | function new_from_config(ports, handle_request, default_options) |
| 189 | if type(handle_request) == "string" then -- COMPAT with old plugins |
| 190 | log("warn", "Old syntax of httpserver.new_from_config being used to register %s", handle_request); |
| 191 | handle_request, default_options = default_options, { base = handle_request }; |
| 192 | end |
| 193 | ports = ports or {5280}; |
| 194 | for _, options in ipairs(ports) do |
| 195 | local port = default_options.port or 5280; |
| 196 | local base = default_options.base; |
| 197 | local ssl = default_options.ssl or false; |
| 198 | local interface = default_options.interface; |
| 199 | if type(options) == "number" then |
| 200 | port = options; |
| 201 | elseif type(options) == "table" then |
| 202 | port = options.port or port; |
| 203 | base = options.path or base; |
| 204 | ssl = options.ssl or ssl; |
| 205 | interface = options.interface or interface; |
| 206 | elseif type(options) == "string" then |
| 207 | base = options; |
| 208 | end |
| 209 | |
| 210 | if ssl then |
| 211 | ssl.mode = "server"; |
| 212 | ssl.protocol = "sslv23"; |
| 213 | ssl.options = "no_sslv2"; |
| 214 | end |
| 215 | |
| 216 | new{ port = port, interface = interface, |
| 217 | base = base, handler = handle_request, |
| 218 | ssl = ssl, type = (ssl and "ssl") or "tcp" }; |
| 219 | end |
| 220 | end |
| 221 | |
| 222 | _M.request_reader = request_reader; |
| 223 | _M.send_response = send_response; |
| 224 | _M.urlencode = urlencode; |
| 225 | |
| 226 | return _M; |