summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEmil Renner Berthing <esmil@mailme.dk>2013-08-19 01:10:11 +0200
committerEmil Renner Berthing <esmil@mailme.dk>2013-08-20 20:53:50 +0200
commit64604cf2c4a3135076c055c4ff1f8c1d3d4d5e37 (patch)
treee13a1c1c9e3f7d9ce7ab9869cbca32e5be84e3e6
parent30a760d9f3b6cd4669b0d915c2d932beda8519d3 (diff)
downloadlem-64604cf2c4a3135076c055c4ff1f8c1d3d4d5e37.tar.gz
lem-64604cf2c4a3135076c055c4ff1f8c1d3d4d5e37.tar.xz
lem-64604cf2c4a3135076c055c4ff1f8c1d3d4d5e37.zip
http: factor out response module
-rw-r--r--lem/hathaway.lua7
-rw-r--r--lem/http/response.lua211
-rw-r--r--lem/http/server.lua196
3 files changed, 240 insertions, 174 deletions
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 <http://www.gnu.org/licenses/>.
+--
+
+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([[
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+<title>Not Found</title>
+</head>
+<body>
+<h1>Not found</h1>
+</body>
+</html>
+]])
+end
+
+function M.htmlerror(num, text)
+ local str = format([[
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+<title>%s</title>
+</head>
+<body>
+<h1>%s</h1>
+</body>
+</html>
+]], 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([[
-<?xml version="1.0" encoding="UTF-8"?>
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
-<head>
-<title>Not Found</title>
-</head>
-<body>
-<h1>Not found</h1>
-</body>
-</html>
-]])
-end
-
-function M.htmlerror(num, text)
- local str = format([[
-<?xml version="1.0" encoding="UTF-8"?>
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
-<head>
-<title>%s</title>
-</head>
-<body>
-<h1>%s</h1>
-</body>
-</html>
-]], 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