From e7f888ccadce6ec68a6da783282bb342785d763d Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Mon, 19 Aug 2013 01:10:11 +0200 Subject: http: factor out response module --- Makefile.in | 3 +- lem/hathaway.lua | 7 +- lem/http/response.lua | 211 ++++++++++++++++++++++++++++++++++++++++++++++++++ lem/http/server.lua | 196 ++++++---------------------------------------- 4 files changed, 242 insertions(+), 175 deletions(-) create mode 100644 lem/http/response.lua diff --git a/Makefile.in b/Makefile.in index 330a8f4..f3c96bf 100644 --- a/Makefile.in +++ b/Makefile.in @@ -28,8 +28,9 @@ llibs = \ lem/io/queue.lua \ lem/lfs.lua \ lem/http.lua \ - lem/http/client.lua \ + lem/http/response.lua \ lem/http/server.lua \ + lem/http/client.lua \ lem/queue.lua \ lem/hathaway.lua diff --git a/lem/hathaway.lua b/lem/hathaway.lua index 7359865..ca43ab3 100644 --- a/lem/hathaway.lua +++ b/lem/hathaway.lua @@ -20,6 +20,7 @@ local format = string.format local httpserv = require 'lem.http.server' +local httpresp = require 'lem.http.response' local M = {} @@ -112,7 +113,7 @@ do if handler then handler(req, res, ok, ...) else - httpserv.method_not_allowed(req, res) + httpresp.method_not_allowed(req, res) end return true end @@ -126,7 +127,7 @@ do if handler then handler(req, res) else - httpserv.method_not_allowed(req, res) + httpresp.method_not_allowed(req, res) end else local i = 0 @@ -134,7 +135,7 @@ do i = i + 1 local entry = lookup[i] if not entry then - httpserv.not_found(req, res) + httpresp.not_found(req, res) break end until check_match(entry, req, res, path:match(entry[1])) diff --git a/lem/http/response.lua b/lem/http/response.lua new file mode 100644 index 0000000..460c09e --- /dev/null +++ b/lem/http/response.lua @@ -0,0 +1,211 @@ +-- +-- This file is part of LEM, a Lua Event Machine. +-- Copyright 2011-2013 Emil Renner Berthing +-- Copyright 2013 Halfdan Mouritzen +-- +-- LEM is free software: you can redistribute it and/or modify it +-- under the terms of the GNU Lesser General Public License as +-- published by the Free Software Foundation, either version 3 of +-- the License, or (at your option) any later version. +-- +-- LEM is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU Lesser General Public License for more details. +-- +-- You should have received a copy of the GNU Lesser General Public +-- License along with LEM. If not, see . +-- + +local setmetatable = setmetatable +local tostring = tostring +local tonumber = tonumber +local pairs = pairs +local type = type +local date = os.date +local format = string.format +local concat = table.concat +local remove = table.remove + +local M = {} + +local status_string = { + -- 1xx Informational + [100] = '100 Continue', + [101] = '101 Switching Protocols', + [102] = '102 Processing', -- WebDAV + + -- 2xx Success + [200] = '200 OK', + [201] = '201 Created', + [202] = '202 Accepted', + [203] = '203 Non-Authoritative Information', + [204] = '204 No Content', + [205] = '205 Reset Content', + [206] = '206 Partial Content', + [207] = '207 Multi-Status', -- WebDAV + [208] = '208 Already Reported', -- WebDAV + [226] = '226 IM Used', + [230] = '230 Authentication Successfull', + + -- 3xx Redirection + [300] = '300 Multiple Choices', + [301] = '301 Moved Permanently', + [302] = '302 Found', + [303] = '303 See Other', + [304] = '304 Not Modified', + [305] = '305 Use Proxy', + [306] = '306 Switch Proxy', + [307] = '307 Temporary Redirect', + [308] = '308 Permanent Redirect', + + -- 4xx Client Error + [400] = '400 Bad Request', + [401] = '401 Unauthorized', + [402] = '402 Payment Required', + [403] = '403 Forbidden', + [404] = '404 Not Found', + [405] = '405 Method Not Allowed', + [406] = '406 Not Acceptable', + [407] = '407 Proxy Authentication Required', + [408] = '408 Request Timeout', + [409] = '409 Conflict', + [410] = '410 Gone', + [411] = '411 Length Required', + [412] = '412 Precondition Failed', + [413] = '413 Request Entity Too Large', + [414] = '414 Request-URI Too Long', + [415] = '415 Unsupported Media Type', + [416] = '416 Requested Range Not Satisfiable', + [417] = '417 Expectation Failed', + -- ... + [422] = '422 Unprocessable Entity', -- WebDAV + [423] = '423 Locked', -- WebDAV + [424] = '424 Failed Dependency', -- WebDAV + -- ... + [426] = '426 Upgrade Required', + [428] = '428 Precondition Required', + [429] = '429 Too Many Requests', + [431] = '431 Request Header Fields Too Large', + + -- 5xx Server Error + [500] = '500 Internal Server Error', + [501] = '501 Not Implemented', + [502] = '502 Bad Gateway', + [503] = '503 Service Unavailable', + [504] = '504 Gateway Timeout', + [505] = '505 HTTP Version Not Supported', + [506] = '506 Variant Also Negotiates', + [507] = '507 Insufficient Storage', -- WebDAV + [508] = '508 Loop Detected', -- WebDAV + -- ... + [510] = '510 Not Extended', + [511] = '511 Network Authentication Required', + [531] = '531 Access Denied', +} + +M.status_string = status_string + +function M.not_found(req, res) + if req.headers['expect'] ~= '100-continue' then + req:body() + end + + res.status = 404 + res.headers['Content-Type'] = 'text/html; charset=UTF-8' + res:add([[ + + + +Not Found + + +

Not found

+ + +]]) +end + +function M.htmlerror(num, text) + local str = format([[ + + + +%s + + +

%s

+ + +]], text, text) + return function(req, res) + res.status = num + res.headers['Content-Type'] = 'text/html; charset=UTF-8' + res.headers['Connection'] = 'close' + res:add(str) + end +end + +M.method_not_allowed = M.htmlerror(405, 'Method Not Allowed') +M.expectation_failed = M.htmlerror(417, 'Expectation Failed') +M.version_not_supported = M.htmlerror(505, 'HTTP Version Not Supported') +M.bad_request = M.htmlerror(400, 'Bad Request') + +local Response = {} +Response.__index = Response +M.Response = Response + +function Response:contentlength() + local len = 0 + for i = 1, #self do + len = len + #self[i] + end + return len +end + +function Response:appendheader(rope, i, size) + if not size then size = 0 end + local line + for k, v in pairs(self.headers) do + line = format('%s: %s\r\n', k, tostring(v)) + i = i + 1 + rope[i] = line + size = size + #line + end + i = i + 1 + rope[i] = '\r\n' + size = size + 2 + return i, size +end + +function Response:appendbody(rope, i, size) + if not size then size = 0 end + local chunk + for j = 1, #self do + chunk = self[j] + i = i + 1 + rope[i] = chunk + size = size + #chunk + end + return i, size +end + +function M.new(req) + local n = 0 + return setmetatable({ + headers = {}, + version = req and req.version or nil, + add = function(self, fmt, first, ...) + n = n + 1 + if first then + self[n] = format(fmt, first, ...) + else + self[n] = fmt + end + end + }, Response) +end + +return M + +-- vim: ts=2 sw=2 noet: diff --git a/lem/http/server.lua b/lem/http/server.lua index 378c8f6..cafab39 100644 --- a/lem/http/server.lua +++ b/lem/http/server.lua @@ -27,133 +27,12 @@ local format = string.format local concat = table.concat local remove = table.remove -local io = require 'lem.io' -require 'lem.http' +local io = require 'lem.io' + require 'lem.http' +local response = require 'lem.http.response' local M = {} -local status_string = { - -- 1xx Informational - [100] = '100 Continue', - [101] = '101 Switching Protocols', - [102] = '102 Processing', -- WebDAV - - -- 2xx Success - [200] = '200 OK', - [201] = '201 Created', - [202] = '202 Accepted', - [203] = '203 Non-Authoritative Information', - [204] = '204 No Content', - [205] = '205 Reset Content', - [206] = '206 Partial Content', - [207] = '207 Multi-Status', -- WebDAV - [208] = '208 Already Reported', -- WebDAV - [226] = '226 IM Used', - [230] = '230 Authentication Successfull', - - -- 3xx Redirection - [300] = '300 Multiple Choices', - [301] = '301 Moved Permanently', - [302] = '302 Found', - [303] = '303 See Other', - [304] = '304 Not Modified', - [305] = '305 Use Proxy', - [306] = '306 Switch Proxy', - [307] = '307 Temporary Redirect', - [308] = '308 Permanent Redirect', - - -- 4xx Client Error - [400] = '400 Bad Request', - [401] = '401 Unauthorized', - [402] = '402 Payment Required', - [403] = '403 Forbidden', - [404] = '404 Not Found', - [405] = '405 Method Not Allowed', - [406] = '406 Not Acceptable', - [407] = '407 Proxy Authentication Required', - [408] = '408 Request Timeout', - [409] = '409 Conflict', - [410] = '410 Gone', - [411] = '411 Length Required', - [412] = '412 Precondition Failed', - [413] = '413 Request Entity Too Large', - [414] = '414 Request-URI Too Long', - [415] = '415 Unsupported Media Type', - [416] = '416 Requested Range Not Satisfiable', - [417] = '417 Expectation Failed', - -- ... - [422] = '422 Unprocessable Entity', -- WebDAV - [423] = '423 Locked', -- WebDAV - [424] = '424 Failed Dependency', -- WebDAV - -- ... - [426] = '426 Upgrade Required', - [428] = '428 Precondition Required', - [429] = '429 Too Many Requests', - [431] = '431 Request Header Fields Too Large', - - -- 5xx Server Error - [500] = '500 Internal Server Error', - [501] = '501 Not Implemented', - [502] = '502 Bad Gateway', - [503] = '503 Service Unavailable', - [504] = '504 Gateway Timeout', - [505] = '505 HTTP Version Not Supported', - [506] = '506 Variant Also Negotiates', - [507] = '507 Insufficient Storage', -- WebDAV - [508] = '508 Loop Detected', -- WebDAV - -- ... - [510] = '510 Not Extended', - [511] = '511 Network Authentication Required', - [531] = '531 Access Denied', -} - -M.status_string = status_string - -function M.not_found(req, res) - if req.headers['expect'] ~= '100-continue' then - req:body() - end - - res.status = 404 - res.headers['Content-Type'] = 'text/html; charset=UTF-8' - res:add([[ - - - -Not Found - - -

Not found

- - -]]) -end - -function M.htmlerror(num, text) - local str = format([[ - - - -%s - - -

%s

- - -]], text, text) - return function(req, res) - res.status = num - res.headers['Content-Type'] = 'text/html; charset=UTF-8' - res.headers['Connection'] = 'close' - res:add(str) - end -end - -M.method_not_allowed = M.htmlerror(405, 'Method Not Allowed') -M.expectation_failed = M.htmlerror(417, 'Expectation Failed') -M.version_not_supported = M.htmlerror(505, 'HTTP Version Not Supported') -M.bad_request = M.htmlerror(400, 'Bad Request') - function M.debug() end do @@ -168,39 +47,20 @@ do end end -do - local Response = {} - Response.__index = Response - M.Response = Response - - function new_response(req) - local n = 0 - return setmetatable({ - headers = {}, - version = req.version, - add = function(self, fmt, first, ...) - n = n + 1 - if first then - self[n] = format(fmt, first, ...) - else - self[n] = fmt - end - end - }, Response) - end - - local function check_match(entry, req, res, ok, ...) - if not ok then return false end - local handler = entry[req.method] - if handler then - handler(req, res, ok, ...) - else - M.method_not_allowed(req, res) - end - return true +local function check_match(entry, req, res, ok, ...) + if not ok then return false end + local handler = entry[req.method] + if handler then + handler(req, res, ok, ...) + else + response.method_not_allowed(req, res) end + return true +end +do local urldecode = M.urldecode + local newresponse = response.new local function handleHTTP(self, client) repeat @@ -210,15 +70,15 @@ do req.path = urldecode(uri:match('^([^?]*)')) - local res = new_response(req) + local res = newresponse(req) if version ~= '1.0' and version ~= '1.1' then - M.version_not_supported(req, res) + response.version_not_supported(req, res) version = '1.1' else local expect = req.headers['expect'] if expect and expect ~= '100-continue' then - M.expectation_failed(req, res) + response.expectation_failed(req, res) else self.handler(req, res) end @@ -232,9 +92,9 @@ do close = true else self.debug('open', err) - res = new_response(req) + res = rewresponse(req) headers = res.headers - M.not_found(req, res) + response.not_found(req, res) end end @@ -272,27 +132,21 @@ do headers['Connection'] = 'close' end - local robe, i = {}, 1 + local rope = {} do local status = res.status if type(status) == 'number' then - status = status_string[status] + status = response.status_string[status] end - robe[1] = format('HTTP/%s %s\r\n', version, status) - end - - for k, v in pairs(headers) do - i = i + 1 - robe[i] = format('%s: %s\r\n', k, tostring(v)) + rope[1] = format('HTTP/%s %s\r\n', version, status) end - i = i + 1 - robe[i] = '\r\n' + res:appendheader(rope, 1) client:cork() - local ok, err = client:write(concat(robe)) + local ok, err = client:write(concat(rope)) if not ok then self.debug('write', err) break end if method ~= 'HEAD' then @@ -320,7 +174,7 @@ do Server.__index = Server M.Server = Server - function Server:run(handler) + function Server:run() return self.socket:autospawn(function(...) return handleHTTP(self, ...) end) end -- cgit v1.2.1