summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAsbjørn Sloth Tønnesen <asbjorn@asbjorn.biz>2012-09-01 21:48:31 +0000
committerAsbjørn Sloth Tønnesen <asbjorn@asbjorn.biz>2012-09-01 21:56:49 +0000
commit1bdf108eecc7e26540e16d2e5522d26ee1db7b42 (patch)
tree2d2f193f64a21cfd64dcc6c5a23444c08c5ff633
parent3ce26416d427c59e6107709b50bdd7dd1f149f3b (diff)
downloadlem-1bdf108eecc7e26540e16d2e5522d26ee1db7b42.tar.gz
lem-1bdf108eecc7e26540e16d2e5522d26ee1db7b42.tar.xz
lem-1bdf108eecc7e26540e16d2e5522d26ee1db7b42.zip
Split basic HTTP protocol handling out of Hathaway
Move HTTP protocol handling into lem.http.server. Keep HTTP request routing in Hathaway. Signed-off-by: Asbjørn Sloth Tønnesen <asbjorn@asbjorn.biz>
-rw-r--r--Makefile.in1
-rw-r--r--lem/hathaway.lua243
-rw-r--r--lem/http/server.lua242
3 files changed, 267 insertions, 219 deletions
diff --git a/Makefile.in b/Makefile.in
index 6caee46..f739623 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -28,6 +28,7 @@ llibs = \
lem/streams.lua \
lem/streams/queue.lua \
lem/http.lua \
+ lem/http/server.lua \
lem/hathaway.lua
clibs = \
diff --git a/lem/hathaway.lua b/lem/hathaway.lua
index 2497232..6d4e2da 100644
--- a/lem/hathaway.lua
+++ b/lem/hathaway.lua
@@ -1,6 +1,7 @@
--
-- This file is part of LEM, a Lua Event Machine.
-- Copyright 2011-2012 Emil Renner Berthing
+-- Copyright 2012 Asbjørn Sloth Tønnesen
--
-- LEM is free software: you can redistribute it and/or
-- modify it under the terms of the GNU General Public License as
@@ -16,61 +17,10 @@
-- 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 streams = require 'lem.streams'
-require 'lem.http'
+local server = require 'lem.http.server'
local M = {}
-local status_string = {
- [100] = '100 Continue',
- [101] = '101 Switching Protocols',
- [102] = '102 Processing', -- WebDAV
-
- [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
-
- [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',
-
- [400] = '400 Bad Request',
- [401] = '401 Unauthorized',
- [402] = '402 Payment Required',
- [403] = '403 Forbidden',
- [404] = '404 Not Found',
- [405] = '405 Method Not Allowed',
- -- ...
- [417] = '417 Expectation Failed',
-
- [500] = '500 Internal Server Error',
- [501] = '501 Not Implemented',
- -- ...
- [505] = '505 HTTP Version Not Supported',
- -- ...
-}
-M.status_string = status_string
-
function M.not_found(req, res)
if req.headers['Expect'] ~= '100-continue' then
req:body()
@@ -91,31 +41,8 @@ function M.not_found(req, res)
]])
end
-do
- local function 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 = htmlerror(405, 'Method Not Allowed')
- M.expectation_failed = htmlerror(417, 'Expectation Failed')
- M.version_not_supported = htmlerror(505, 'HTTP Version Not Supported')
-end
+M.status_string = server.status_string
+M.method_not_allowed = server.method_not_allowed
function M.debug() end
@@ -198,23 +125,6 @@ do
M.DELETEM = match_setter('DELETE')
end
- local Response = {}
- Response.__index = Response
- M.Response = Response
-
- function new_response(req)
- local n = 0
- return setmetatable({
- headers = {},
- status = 200,
- version = req.version,
- add = function(self, ...)
- n = n + 1
- self[n] = format(...)
- end
- }, Response)
- end
-
local function check_match(entry, req, res, ok, ...)
if not ok then return false end
local handler = entry[req.method]
@@ -226,140 +136,35 @@ do
return true
end
- local function handler(istream, ostream)
- repeat
- local req, err = istream:read('HTTPRequest')
- if not req then M.debug(err) break end
- local method, uri, version = req.method, req.uri, req.version
- M.debug(format("%s %s HTTP/%s", method, uri, version))
-
- req.ostream = ostream
- local res = new_response(req)
-
- if version ~= '1.0' and version ~= '1.1' then
- M.version_not_supported(req, res)
- version = '1.1'
+ local function handler(req, res)
+ local method, uri, version = req.method, req.uri, req.version
+ local path = lookup[uri]
+ if path then
+ local handler = path[method]
+ if handler then
+ handler(req, res)
else
- local expect = req.headers['Expect']
- if expect and expect ~= '100-continue' then
- M.expectation_failed(req, res)
- else
- local path = lookup[uri]
- if path then
- local handler = path[method]
- if handler then
- handler(req, res)
- else
- M.method_not_allowed(req, res)
- end
- else
- local i = 0
- repeat
- i = i + 1
- local entry = lookup[i]
- if not entry then
- M.not_found(req, res)
- break
- end
- until check_match(entry, req, res, uri:match(entry[1]))
- end
- end
- end
-
- local headers = res.headers
- local file, close = res.file, false
- if type(file) == 'string' then
- file, err = streams.sendfile(file)
- if file then
- close = true
- else
- M.debug(err)
- res = new_response(req)
- headers = res.headers
- M.not_found(req, res)
- end
- end
-
- if res.status == 200 and #res == 0 and res.file == nil then
- res.status = 204
- elseif headers['Content-Length'] == nil then
- local len
- if file then
- len = file:size()
- else
- len = 0
- for i = 1, #res do
- len = len + #res[i]
- end
- end
-
- headers['Content-Length'] = len
- end
-
- if headers['Date'] == nil then
- headers['Date'] = date('!%a, %d %b %Y %T GMT')
- end
-
- if headers['Server'] == nil then
- headers['Server'] = 'Hathaway/0.1 LEM/0.1'
- end
-
- local robe, i = {}, 1
- do
- local status = res.status
- if type(status) == 'number' then
- status = status_string[status]
- end
-
- robe[1] = format('HTTP/%s %s\r\n', version, status)
+ M.method_not_allowed(req, res)
end
-
- for k, v in pairs(headers) do
+ else
+ local i = 0
+ repeat
i = i + 1
- robe[i] = format('%s: %s\r\n', k, tostring(v))
- end
-
- i = i + 1
- robe[i] = '\r\n'
-
- local ok, err = ostream:cork()
- if not ok then M.debug(err) break end
-
- local ok, err = ostream:write(concat(robe))
- if not ok then M.debug(err) break end
-
- if method ~= 'HEAD' then
- if file then
- ok, err = ostream:sendfile(file)
- if close then file:close() end
- else
- ok, err = ostream:write(concat(res))
+ local entry = lookup[i]
+ if not entry then
+ M.not_found(req, res)
+ break
end
- if not ok then M.debug(err) break end
- end
-
- local ok, err = ostream:uncork()
- if not ok then M.debug(err) break end
-
- until version == '1.0'
- or req.headers['Connection'] == 'close'
- or headers['Connection'] == 'close'
-
- istream:close()
- ostream:close()
+ until check_match(entry, req, res, uri:match(entry[1]))
+ end
end
function M.Hathaway(address, port)
- local server, err = streams.tcp4_listen(address, port)
- if not server then M.debug(err) return nil, err end
+ local ret, err = server.tcp4_listen(address, port, handler)
+ if not ret then M.debug(err) return nil, err end
- M.server = server
+ M.server = server.server
- local ok, err = server:autospawn(handler)
- if not ok and err ~= 'interrupted' then
- M.debug(err)
- return nil, err
- end
return true
end
end
diff --git a/lem/http/server.lua b/lem/http/server.lua
new file mode 100644
index 0000000..2e15cd0
--- /dev/null
+++ b/lem/http/server.lua
@@ -0,0 +1,242 @@
+--
+-- This file is part of LEM, a Lua Event Machine.
+-- Copyright 2011-2012 Emil Renner Berthing
+-- Copyright 2012 Asbjørn Sloth Tønnesen
+--
+-- LEM is free software: you can redistribute it and/or
+-- modify it under the terms of the GNU 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 General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with LEM. If not, see <http://www.gnu.org/licenses/>.
+--
+
+local setmetatable = setmetatable
+local tostring = tostring
+local pairs = pairs
+local type = type
+local date = os.date
+local format = string.format
+local concat = table.concat
+
+local streams = require 'lem.streams'
+require 'lem.http'
+
+local M = {}
+
+local status_string = {
+ [100] = '100 Continue',
+ [101] = '101 Switching Protocols',
+ [102] = '102 Processing', -- WebDAV
+
+ [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
+
+ [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',
+
+ [400] = '400 Bad Request',
+ [401] = '401 Unauthorized',
+ [402] = '402 Payment Required',
+ [403] = '403 Forbidden',
+ [404] = '404 Not Found',
+ [405] = '405 Method Not Allowed',
+ -- ...
+ [417] = '417 Expectation Failed',
+
+ [500] = '500 Internal Server Error',
+ [501] = '501 Not Implemented',
+ -- ...
+ [505] = '505 HTTP Version Not Supported',
+ -- ...
+}
+M.status_string = status_string
+
+do
+ local function 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 = htmlerror(405, 'Method Not Allowed')
+ M.expectation_failed = htmlerror(417, 'Expectation Failed')
+ M.version_not_supported = htmlerror(505, 'HTTP Version Not Supported')
+end
+
+function M.debug() end
+
+do
+ local Response = {}
+ Response.__index = Response
+ M.Response = Response
+
+ function new_response(req)
+ local n = 0
+ return setmetatable({
+ headers = {},
+ status = 200,
+ version = req.version,
+ add = function(self, ...)
+ n = n + 1
+ self[n] = format(...)
+ end
+ }, Response)
+ end
+
+ local function handler(istream, ostream)
+ repeat
+ local req, err = istream:read('HTTPRequest')
+ if not req then M.debug(err) break end
+ local method, uri, version = req.method, req.uri, req.version
+ M.debug(format("%s %s HTTP/%s", method, uri, version))
+
+ req.ostream = ostream
+ local res = new_response(req)
+
+ if version ~= '1.0' and version ~= '1.1' then
+ M.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)
+ else
+ M.callback(req, res)
+ end
+ end
+
+ local headers = res.headers
+ local file, close = res.file, false
+ if type(file) == 'string' then
+ file, err = streams.sendfile(file)
+ if file then
+ close = true
+ else
+ M.debug(err)
+ res = new_response(req)
+ headers = res.headers
+ M.not_found(req, res)
+ end
+ end
+
+ if res.status == 200 and #res == 0 and res.file == nil then
+ res.status = 204
+ elseif headers['Content-Length'] == nil then
+ local len
+ if file then
+ len = file:size()
+ else
+ len = 0
+ for i = 1, #res do
+ len = len + #res[i]
+ end
+ end
+
+ headers['Content-Length'] = len
+ end
+
+ if headers['Date'] == nil then
+ headers['Date'] = date('!%a, %d %b %Y %T GMT')
+ end
+
+ if headers['Server'] == nil then
+ headers['Server'] = 'Hathaway/0.1 LEM/0.1'
+ end
+
+ local robe, i = {}, 1
+ do
+ local status = res.status
+ if type(status) == 'number' then
+ status = 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))
+ end
+
+ i = i + 1
+ robe[i] = '\r\n'
+
+ local ok, err = ostream:cork()
+ if not ok then M.debug(err) break end
+
+ local ok, err = ostream:write(concat(robe))
+ if not ok then M.debug(err) break end
+
+ if method ~= 'HEAD' then
+ if file then
+ ok, err = ostream:sendfile(file)
+ if close then file:close() end
+ else
+ ok, err = ostream:write(concat(res))
+ end
+ if not ok then M.debug(err) break end
+ end
+
+ local ok, err = ostream:uncork()
+ if not ok then M.debug(err) break end
+
+ until version == '1.0'
+ or req.headers['Connection'] == 'close'
+ or headers['Connection'] == 'close'
+
+ istream:close()
+ ostream:close()
+ end
+
+ function M.tcp4_listen(address, port, callback)
+ local server, err = streams.tcp4_listen(address, port)
+ if not server then M.debug(err) return nil, err end
+
+ M.server = server
+ M.callback = callback
+
+ local ok, err = server:autospawn(handler)
+ if not ok and err ~= 'interrupted' then
+ M.debug(err)
+ return nil, err
+ end
+ return true
+ end
+end
+
+return M
+
+-- vim: ts=2 sw=2 noet: