--
-- 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 io = require 'lem.io'
require 'lem.http'
local response = require 'lem.http.response'
local M = {}
function M.debug() end
local Request = {}
Request.__index = Request
M.Request = Request
function Request:body()
local len, body = self.headers['content-length'], ''
if not len then return body end
len = tonumber(len)
if len <= 0 then return body end
if self.headers['expect'] == '100-continue' then
local ok, err = self.client:write('HTTP/1.1 100 Continue\r\n\r\n')
if not ok then return nil, err end
end
local err
body, err = self.client:read(len)
if not body then return nil, err end
return body
end
do
local gsub, char, tonumber = string.gsub, string.char, tonumber
local function tochar(str)
return char(tonumber(str, 16))
end
function M.urldecode(str)
return gsub(gsub(str, '+', ' '), '%%(%x%x)', tochar)
end
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
response.method_not_allowed(req, res)
end
return true
end
local urldecode = M.urldecode
local newresponse = response.new
local function handleHTTP(self, client)
repeat
local req, err = client:read('HTTPRequest')
if not req then self.debug('read', err) break end
local method, uri, version = req.method, req.uri, req.version
setmetatable(req, Request)
req.client = client
req.path = urldecode(uri:match('^([^?]*)'))
local res = newresponse(req)
if version ~= '1.0' and version ~= '1.1' then
response.version_not_supported(req, res)
version = '1.1'
else
local expect = req.headers['expect']
if expect and expect ~= '100-continue' then
response.expectation_failed(req, res)
else
self.handler(req, res)
end
end
local headers = res.headers
local file, close = res.file, false
if type(file) == 'string' then
file, err = io.open(file)
if file then
close = true
else
self.debug('open', err)
res = newresponse(req)
headers = res.headers
response.not_found(req, res)
end
end
if not res.status then
if #res == 0 and file == nil then
res.status = 204
else
res.status = 200
end
end
if headers['Content-Length'] == nil and res.status ~= 204 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.3'
end
if req.headers['connection'] == 'close' and headers['Connection'] == nil then
headers['Connection'] = 'close'
end
local rope = {}
do
local status = res.status
if type(status) == 'number' then
status = response.status_string[status]
end
rope[1] = format('HTTP/%s %s\r\n', version, status)
end
res:appendheader(rope, 1)
client:cork()
local ok, err = client:write(concat(rope))
if not ok then self.debug('write', err) break end
if method ~= 'HEAD' then
if file then
ok, err = client:sendfile(file, headers['Content-Length'])
if close then file:close() end
else
local body = concat(res)
if #body > 0 then
ok, err = client:write(body)
end
end
if not ok then self.debug('write', err) break end
end
client:uncork()
until version == '1.0' or headers['Connection'] == 'close'
client:close()
end
local Server = {}
Server.__index = Server
M.Server = Server
function Server:run()
return self.socket:autospawn(function(...) return handleHTTP(self, ...) end)
end
function Server:close()
return self.socket:close()
end
local type, setmetatable = type, setmetatable
function M.new(host, port, handler)
local socket, err
if type(host) == 'string' then
socket, err = io.tcp.listen(host, port)
if not socket then
return nil, err
end
else
socket = host
handler = port
end
return setmetatable({
socket = socket,
handler = handler,
debug = M.debug
}, Server)
end
return M
-- vim: ts=2 sw=2 noet: