X-Git-Url: https://git.enpas.org/?a=blobdiff_plain;f=net%2Fhttpserver.lua;h=74f61c56a46182b27d654b6b17cfab77a8d6df73;hb=9219b5b35c5be9687eafac1f840246c10352905e;hp=699e0324dfcb35565fd5a3e46bd3ad6c61421292;hpb=40ec7f894bbea6c50bb014fbd5d203ea781dfe79;p=prosody.git diff --git a/net/httpserver.lua b/net/httpserver.lua index 699e0324..74f61c56 100644 --- a/net/httpserver.lua +++ b/net/httpserver.lua @@ -1,25 +1,26 @@ -- Prosody IM --- Copyright (C) 2008-2009 Matthew Wild --- Copyright (C) 2008-2009 Waqas Hussain +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -local socket = require "socket" local server = require "net.server" local url_parse = require "socket.url".parse; +local httpstream_new = require "util.httpstream".new; local connlisteners_start = require "net.connlisteners".start; local connlisteners_get = require "net.connlisteners".get; local listener; local t_insert, t_concat = table.insert, table.concat; -local s_match, s_gmatch = string.match, string.gmatch; local tonumber, tostring, pairs, ipairs, type = tonumber, tostring, pairs, ipairs, type; +local xpcall = xpcall; +local debug_traceback = debug.traceback; -local urlencode = function (s) return s and (s:gsub("%W", function (c) return string.format("%%%02x", c:byte()); end)); end +local urlencode = function (s) return s and (s:gsub("%W", function (c) return ("%%%02x"):format(c:byte()); end)); end local log = require "util.logger".init("httpserver"); @@ -29,48 +30,41 @@ module "httpserver" local default_handler; -local function expectbody(reqt) - return reqt.method == "POST"; -end - local function send_response(request, response) -- Write status line local resp; - if response.body then + if response.body or response.headers then + local body = response.body and tostring(response.body); log("debug", "Sending response to %s", request.id); - resp = { "HTTP/1.0 ", response.status or "200 OK", "\r\n"}; + resp = { "HTTP/1.0 "..(response.status or "200 OK").."\r\n" }; local h = response.headers; if h then for k, v in pairs(h) do - t_insert(resp, k); - t_insert(resp, ": "); - t_insert(resp, v); - t_insert(resp, "\r\n"); + t_insert(resp, k..": "..v.."\r\n"); end end - if response.body and not (h and h["Content-Length"]) then - t_insert(resp, "Content-Length: "); - t_insert(resp, #response.body); - t_insert(resp, "\r\n"); + if body and not (h and h["Content-Length"]) then + t_insert(resp, "Content-Length: "..#body.."\r\n"); end t_insert(resp, "\r\n"); - if response.body and request.method ~= "HEAD" then - t_insert(resp, response.body); + if body and request.method ~= "HEAD" then + t_insert(resp, body); end + request.write(t_concat(resp)); else -- Response we have is just a string (the body) - log("debug", "Sending response to %s: %s", request.id or "", response or ""); + log("debug", "Sending 200 response to %s", request.id or ""); - resp = { "HTTP/1.0 200 OK\r\n" }; - t_insert(resp, "Connection: close\r\n"); - t_insert(resp, "Content-Length: "); - t_insert(resp, #response); - t_insert(resp, "\r\n\r\n"); + local resp = "HTTP/1.0 200 OK\r\n" + .. "Connection: close\r\n" + .. "Content-Type: text/html\r\n" + .. "Content-Length: "..#response.."\r\n" + .. "\r\n" + .. response; - t_insert(resp, response); + request.write(resp); end - request.write(t_concat(resp)); if not request.stayopen then request:destroy(); end @@ -88,11 +82,24 @@ local function call_callback(request, err) end callback = (request.server and request.server.handlers[base]) or default_handler; - if callback == default_handler then - log("debug", "Default callback for this request (base: "..tostring(base)..")") - end end if callback then + local _callback = callback; + function callback(method, body, request) + local ok, result = xpcall(function() return _callback(method, body, request) end, debug_traceback); + if ok then return result; end + log("error", "Error in HTTP server handler: %s", result); + -- TODO: When we support pipelining, request.destroyed + -- won't be the right flag - we just want to see if there + -- has been a response to this request yet. + if not request.destroyed then + return { + status = "500 Internal Server Error"; + headers = { ["Content-Type"] = "text/plain" }; + body = "There was an error processing your request. See the error log for more details."; + }; + end + end if err then log("debug", "Request error: "..err); if not callback(nil, err, request) then @@ -120,99 +127,36 @@ local function call_callback(request, err) end local function request_reader(request, data, startpos) - if not data then - if request.body then - call_callback(request); - else - -- Error.. connection was closed prematurely - call_callback(request, "connection-closed"); - end - -- Here we force a destroy... the connection is gone, so we can't reply later - destroy_request(request); - return; - end - if request.state == "body" then - log("debug", "Reading body...") - if not request.body then request.body = {}; request.havebodylength, request.bodylength = 0, tonumber(request.headers["content-length"]); end - if startpos then - data = data:sub(startpos, -1) - end - t_insert(request.body, data); - if request.bodylength then - request.havebodylength = request.havebodylength + #data; - if request.havebodylength >= request.bodylength then - -- We have the body - call_callback(request); - end - end - elseif request.state == "headers" then - log("debug", "Reading headers...") - local pos = startpos; - local headers = request.headers or {}; - for line in data:gmatch("(.-)\r\n") do - startpos = (startpos or 1) + #line + 2; - local k, v = line:match("(%S+): (.+)"); - if k and v then - headers[k:lower()] = v; --- log("debug", "Header: "..k:lower().." = "..v); - elseif #line == 0 then - request.headers = headers; - break; - else - log("debug", "Unhandled header line: "..line); - end - end - - if not expectbody(request) then + if not request.parser then + local function success_cb(r) + for k,v in pairs(r) do request[k] = v; end + request.url = url_parse(request.path); + request.url.path = request.url.path and request.url.path:gsub("%%(%x%x)", function(x) return x.char(tonumber(x, 16)) end); + request.body = { request.body }; call_callback(request); - return; end - - -- Reached the end of the headers - request.state = "body"; - if #data > startpos then - return request_reader(request, data:sub(startpos, -1)); - end - elseif request.state == "request" then - log("debug", "Reading request line...") - local method, path, http, linelen = data:match("^(%S+) (%S+) HTTP/(%S+)\r\n()", startpos); - if not method then - return call_callback(request, "invalid-status-line"); - end - - request.method, request.path, request.httpversion = method, path, http; - - request.url = url_parse(request.path); - - log("debug", method.." request for "..tostring(request.path) .. " on port "..request.handler.serverport()); - - if request.onlystatus then - if not call_callback(request) then - return; - end - end - - request.state = "headers"; - - if #data > linelen then - return request_reader(request, data:sub(linelen, -1)); + local function error_cb(r) + call_callback(request, r or "connection-closed"); + destroy_request(request); end + request.parser = httpstream_new(success_cb, error_cb); end + request.parser:feed(data); end -- The default handler for requests default_handler = function (method, body, request) - log("debug", method.." request for "..tostring(request.path) .. " on port "..request.handler.serverport()); - return { status = "404 Not Found", + log("debug", method.." request for "..tostring(request.path) .. " on port "..request.handler:serverport()); + return { status = "404 Not Found", headers = { ["Content-Type"] = "text/html" }, body = "Page Not FoundNot here :(" }; end function new_request(handler) - return { handler = handler, conn = handler.socket, - write = handler.write, state = "request", - server = http_servers[handler.serverport()], + return { handler = handler, conn = handler, + write = function (...) return handler:write(...); end, state = "request", + server = http_servers[handler:serverport()], send = send_response, destroy = destroy_request, id = tostring{}:match("%x+$") @@ -230,9 +174,9 @@ function destroy_request(request) else log("debug", "Request has no destroy callback"); end - request.handler.close() + request.handler:close() if request.conn then - listener.disconnect(request.conn, "closed"); + listener.ondisconnect(request.conn, "closed"); end end end @@ -250,13 +194,28 @@ function new(params) end end -function new_from_config(ports, default_base, handle_request) +function set_default_handler(handler) + default_handler = handler; +end + +function new_from_config(ports, handle_request, default_options) + if type(handle_request) == "string" then -- COMPAT with old plugins + log("warn", "Old syntax of httpserver.new_from_config being used to register %s", handle_request); + handle_request, default_options = default_options, { base = handle_request }; + end + ports = ports or {5280}; for _, options in ipairs(ports) do - local port, base, ssl, interface = 5280, default_base, false, nil; + local port = default_options.port or 5280; + local base = default_options.base; + local ssl = default_options.ssl or false; + local interface = default_options.interface; if type(options) == "number" then port = options; elseif type(options) == "table" then - port, base, ssl, interface = options.port or 5280, options.path or default_base, options.ssl or false, options.interface; + port = options.port or port; + base = options.path or base; + ssl = options.ssl or ssl; + interface = options.interface or interface; elseif type(options) == "string" then base = options; end @@ -264,9 +223,12 @@ function new_from_config(ports, default_base, handle_request) if ssl then ssl.mode = "server"; ssl.protocol = "sslv23"; + ssl.options = "no_sslv2"; end - new{ port = port, base = base, handler = handle_request, ssl = ssl, type = (ssl and "ssl") or "tcp" } + new{ port = port, interface = interface, + base = base, handler = handle_request, + ssl = ssl, type = (ssl and "ssl") or "tcp" }; end end