summaryrefslogtreecommitdiffhomepage
path: root/libs/httpd/luasrc
diff options
context:
space:
mode:
authorSteven Barth <steven@midlink.org>2008-06-23 18:17:02 +0000
committerSteven Barth <steven@midlink.org>2008-06-23 18:17:02 +0000
commit4f630d647c7191ac9d8fd2dfcc8e93746faf391a (patch)
tree76bb12773e833e38c69ee43f006b78f0e84ab748 /libs/httpd/luasrc
parenta038da390d7fd3a75b5a1045afbb4d9782556c57 (diff)
* Introducing LuCI HTTPD as testing environment
* Several coroutine-safety fixes
Diffstat (limited to 'libs/httpd/luasrc')
-rw-r--r--libs/httpd/luasrc/httpd.lua57
-rw-r--r--libs/httpd/luasrc/httpd/handler/file.lua16
-rw-r--r--libs/httpd/luasrc/httpd/handler/luci.lua56
-rw-r--r--libs/httpd/luasrc/httpd/module.lua26
-rw-r--r--libs/httpd/luasrc/httpd/server.lua163
5 files changed, 222 insertions, 96 deletions
diff --git a/libs/httpd/luasrc/httpd.lua b/libs/httpd/luasrc/httpd.lua
index 8cd946f33..82f1be97d 100644
--- a/libs/httpd/luasrc/httpd.lua
+++ b/libs/httpd/luasrc/httpd.lua
@@ -13,23 +13,41 @@ $Id$
]]--
-require("ltn12")
+module("luci.httpd", package.seeall)
require("socket")
require("luci.util")
+function Socket(ip, port)
+ local sock, err = socket.bind( ip, port )
+
+ if sock then
+ sock:settimeout( 0, "t" )
+ end
+
+ return sock, err
+end
+
Daemon = luci.util.class()
-function Daemon.__init__(self, threadlimit)
+function Daemon.__init__(self, threadlimit, timeout)
self.reading = {}
self.running = {}
self.handler = {}
+ self.debug = false
self.threadlimit = threadlimit
+ self.timeout = timeout or 0.1
end
-function Daemon.register(self, socket, clhandler, errhandler)
- table.insert( self.reading, socket )
- self.handler[socket] = { clhandler = clhandler, errhandler = errhandler }
+function Daemon.dprint(self, msg)
+ if self.debug then
+ io.stderr:write("[daemon] " .. msg .. "\n")
+ end
+end
+
+function Daemon.register(self, sock, clhandler, errhandler)
+ table.insert( self.reading, sock )
+ self.handler[sock] = { clhandler = clhandler, errhandler = errhandler }
end
function Daemon.run(self)
@@ -39,7 +57,11 @@ function Daemon.run(self)
end
function Daemon.step(self)
- local input = socket.select( self.reading, nil, 0 )
+ local input, output, err = socket.select( self.reading, nil, 0 )
+
+ if err == "timeout" and #self.running == 0 then
+ socket.sleep(self.timeout)
+ end
-- accept new connections
for i, connection in ipairs(input) do
@@ -47,19 +69,25 @@ function Daemon.step(self)
local sock = connection:accept()
-- check capacity
- if self.threadlimit and #running < self.threadlimit then
+ if not self.threadlimit or #self.running < self.threadlimit then
+
+ self:dprint("Accepted incoming connection from " .. sock:getpeername())
table.insert( self.running, {
coroutine.create( self.handler[connection].clhandler ),
sock
} )
+ self:dprint("Created " .. tostring(self.running[#self.running][1]))
+
-- reject client
else
+ self:dprint("Rejected incoming connection from " .. sock:getpeername())
+
if self.handler[connection].errhandler then
self.handler[connection].errhandler( sock )
end
-
+
sock:close()
end
end
@@ -69,9 +97,18 @@ function Daemon.step(self)
-- reap dead clients
if coroutine.status( client[1] ) == "dead" then
+ self:dprint("Completed " .. tostring(client[1]))
table.remove( self.running, i )
- end
+ else
+ self:dprint("Resuming " .. tostring(client[1]))
+
+ local stat, err = coroutine.resume( client[1], client[2] )
+
+ self:dprint(tostring(client[1]) .. " returned")
- coroutine.resume( client[1], client[2] )
+ if not stat then
+ self:dprint("Error in " .. tostring(client[1]) .. " " .. err)
+ end
+ end
end
end
diff --git a/libs/httpd/luasrc/httpd/handler/file.lua b/libs/httpd/luasrc/httpd/handler/file.lua
index eb5aafd78..83549f338 100644
--- a/libs/httpd/luasrc/httpd/handler/file.lua
+++ b/libs/httpd/luasrc/httpd/handler/file.lua
@@ -12,11 +12,17 @@ function Simple.__init__(self, docroot)
end
function Simple.handle(self, request, sourcein, sinkerr)
- local file = self.docroot .. request.env.REQUEST_URI:gsub("../", "")
- local size = luci.fs.stat(file, "size")
- if size then
- return Response(200, {["Content-Length"] = size}), ltn12.source.file(io.open(file))
+ local uri = request.env.PATH_INFO
+ local file = self.docroot .. uri:gsub("%.%./", "")
+ local stat = luci.fs.stat(file)
+
+ if stat then
+ if stat.type == "regular" then
+ return Response(200, {["Content-Length"] = stat.size}), ltn12.source.file(io.open(file))
+ else
+ return self:failure(403, "Unable to transmit " .. stat.type .. " " .. uri)
+ end
else
- return Response(404)
+ return self:failure(404, "No such file: " .. uri)
end
end \ No newline at end of file
diff --git a/libs/httpd/luasrc/httpd/handler/luci.lua b/libs/httpd/luasrc/httpd/handler/luci.lua
new file mode 100644
index 000000000..e4916bd2c
--- /dev/null
+++ b/libs/httpd/luasrc/httpd/handler/luci.lua
@@ -0,0 +1,56 @@
+module("luci.httpd.handler.luci", package.seeall)
+require("luci.dispatcher")
+require("luci.http")
+require("ltn12")
+
+Luci = luci.util.class(luci.httpd.module.Handler)
+Response = luci.httpd.module.Response
+
+function Luci.__init__(self)
+ luci.httpd.module.Handler.__init__(self)
+end
+
+function Luci.handle(self, request, sourcein, sinkerr)
+ local r = luci.http.Request(
+ request.env,
+ sourcein,
+ sinkerr
+ )
+
+ local res, id, data1, data2 = true, 0, nil, nil
+ local headers = {}
+ local status = 200
+
+ local x = coroutine.create(luci.dispatcher.httpdispatch)
+ while id < 3 do
+ coroutine.yield()
+
+ res, id, data1, data2 = coroutine.resume(x, r)
+
+ if not res then
+ status = 500
+ headers["Content-Type"] = "text/plain"
+ local err = {id}
+ return status, headers, function() local x = table.remove(err) return x end
+ end
+
+ if id == 1 then
+ status = data1
+ elseif id == 2 then
+ headers[data1] = data2
+ end
+ end
+
+ local function iter()
+ local res, id, data = coroutine.resume(x)
+ if not res then
+ return nil, id
+ elseif id == 5 then
+ return nil
+ else
+ return data
+ end
+ end
+
+ return Response(status, headers), iter
+end \ No newline at end of file
diff --git a/libs/httpd/luasrc/httpd/module.lua b/libs/httpd/luasrc/httpd/module.lua
index 28460a1c9..c321856a8 100644
--- a/libs/httpd/luasrc/httpd/module.lua
+++ b/libs/httpd/luasrc/httpd/module.lua
@@ -34,15 +34,9 @@ end
-- Creates a failure reply
-function Handler.failure(self, message)
- response = {
- status = 500,
- headers = {
- ["Content-Type"] = "text/plain"
- }
- }
-
- sourceout = ltn12.source.string(message)
+function Handler.failure(self, code, message)
+ local response = Response(code, { ["Content-Type"] = "text/plain" })
+ local sourceout = ltn12.source.string(message)
return response, sourceout
end
@@ -70,12 +64,12 @@ function Handler.process(self, request, sourcein, sinkout, sinkerr)
-- Check for any errors
if not stat then
- response, sourceout = self:failure(response)
+ response, sourceout = self:failure(500, response)
end
-- Check data
if not luci.util.instanceof(response, Response) then
- response, sourceout = self:failure("Core error: Invalid module response!")
+ response, sourceout = self:failure(500, "Core error: Invalid module response!")
end
-- Process outgoing filters
@@ -131,12 +125,4 @@ end
function Response.setstatus(self, status)
self.status = status
-end
-
-
--- Status codes
-statusmsg = {
- [200] = "OK",
- [404] = "Not Found",
- [500] = "Internal Server Error",
-} \ No newline at end of file
+end \ No newline at end of file
diff --git a/libs/httpd/luasrc/httpd/server.lua b/libs/httpd/luasrc/httpd/server.lua
index 7f973ac03..2bb44bd02 100644
--- a/libs/httpd/luasrc/httpd/server.lua
+++ b/libs/httpd/luasrc/httpd/server.lua
@@ -18,6 +18,7 @@ require("luci.util")
READ_BUFSIZE = 1024
+
VHost = luci.util.class()
function VHost.__init__(self, handler)
@@ -25,76 +26,75 @@ function VHost.__init__(self, handler)
self.dhandler = {}
end
-function VHost.process(self, ...)
- -- TODO: Dispatch handler
-end
-
-function VHost.sethandler(self, handler, match)
- if match then
- self.dhandler[match] = handler
- else
- self.handler = handler
- end
-end
+function VHost.process(self, request, sourcein, sinkout, sinkerr)
+ local handler = self.handler
+ local uri = request.env.REQUEST_URI:match("^([^?]*)")
+ -- SCRIPT_NAME
+ request.env.SCRIPT_NAME = ""
-Server = luci.util.class()
+ -- Call URI part
+ request.env.PATH_INFO = uri
-function Server.__init__(self, ip, port, base)
- self.socket = socket.bind(ip, port)
- self.socket:settimeout(0, "t")
- self.clhandler = client_handler
- self.errhandler = error503
- self.host = nil
- self.vhosts = {}
-
- -- Clone another server
- if base then
- getmetatable(self).__index = base
+ for k, dhandler in pairs(self.dhandler) do
+ if k == uri or k.."/" == uri:sub(1, #k+1) then
+ handler = dhandler
+ request.env.SCRIPT_NAME = k
+ request.env.PATH_INFO = uri:sub(#k+1)
+ break;
+ end
end
-end
--- Sets a vhost
-function Server.setvhost(self, vhost, name)
- if name then
- self.vhosts[name] = vhost
+ if handler then
+ handler:process(request, sourcein, sinkout, sinkerr)
+ return true
else
- self.host = vhost
+ return false
end
end
-function Server.error400(self, client, msg)
- client:send( "HTTP/1.0 400 Bad request\r\n" )
- client:send( "Content-Type: text/plain\r\n\r\n" )
+function VHost.set_default_handler(self, handler)
+ self.handler = handler
+end
- if msg then
- client:send( msg .. "\r\n" )
- end
- client:close()
+function VHost.set_handler(self, match, handler)
+ self.dhandler[match] = handler
end
-function Server.error503(self, client)
- client:send( "HTTP/1.0 503 Server unavailable\r\n" )
- client:send( "Content-Type: text/plain\r\n\r\n" )
- client:send( "There are too many clients connected, try again later\r\n" )
-end
-function Server.process(self, ...)
- -- TODO: Dispatch vhost
+
+Server = luci.util.class()
+
+function Server.__init__(self, host)
+ self.clhandler = client_handler
+ self.errhandler = error503
+ self.host = host
+ self.vhosts = {}
end
+function Server.set_default_vhost(self, vhost)
+ self.host = vhost
+end
-function Server.client_handler(self, client)
+-- Sets a vhost
+function Server.set_vhost(self, name, vhost)
+ self.vhosts[name] = vhost
+end
- client:settimeout( 0 )
+function Server.create_daemon_handlers(self)
+ return function(...) return self:process(...) end,
+ function(...) return self:error503(...) end
+end
+function Server.create_client_sources(self, client)
-- Create LTN12 block source
local block_source = function()
- coroutine.yield()
+ -- Yielding here may cause chaos in coroutine based modules, be careful
+ -- coroutine.yield()
local chunk, err, part = client:receive( READ_BUFSIZE )
@@ -108,6 +108,7 @@ function Server.client_handler(self, client)
end
+
-- Create LTN12 line source
local line_source = ltn12.source.simplify( function()
@@ -139,14 +140,55 @@ function Server.client_handler(self, client)
end
end )
- coroutine.yield(client)
+ return block_source, line_source
+end
- -- parse message
- local message, err = luci.http.protocol.parse_message_header( line_source )
+function Server.error400(self, socket, msg)
+ socket:send( "HTTP/1.0 400 Bad request\r\n" )
+ socket:send( "Content-Type: text/plain\r\n\r\n" )
- if message then
+ if msg then
+ socket:send( msg .. "\r\n" )
+ end
+
+ socket:close()
+end
+
+function Server.error500(self, socket, msg)
+ socket:send( "HTTP/1.0 500 Internal Server Error\r\n" )
+ socket:send( "Content-Type: text/plain\r\n\r\n" )
+
+ if msg then
+ socket:send( msg .. "\r\n" )
+ end
+
+ socket:close()
+end
+
+function Server.error503(self, socket)
+ socket:send( "HTTP/1.0 503 Server unavailable\r\n" )
+ socket:send( "Content-Type: text/plain\r\n\r\n" )
+ socket:send( "There are too many clients connected, try again later\r\n" )
+ socket:close()
+end
+
+function Server.process(self, client)
+
+ client:settimeout( 0 )
+ local sourcein, sourcehdr = self:create_client_sources(client)
+ local sinkerr = ltn12.sink.file(io.stderr)
+
+ -- FIXME: Add keep-alive support
+ local sinkout = socket.sink("close-when-done", client)
+
+ coroutine.yield()
+
+ -- parse headers
+ local message, err = luci.http.protocol.parse_message_header( sourcehdr )
+
+ if message then
-- If we have a HTTP/1.1 client and an Expect: 100-continue header then
-- respond with HTTP 100 Continue message
if message.http_version == 1.1 and message.headers['Expect'] and
@@ -155,19 +197,18 @@ function Server.client_handler(self, client)
client:send("HTTP/1.1 100 Continue\r\n\r\n")
end
-
- local s, e = luci.http.protocol.parse_message_body( block_source, message )
-
- -- XXX: debug
- luci.util.dumptable( message )
-
- if not s and e then
- self:error400( client, e )
+ local host = self.vhosts[message.env.HTTP_HOST] or self.host
+ if host then
+ if host:process(message, sourcein, sinkout, sinkerr) then
+ sinkout()
+ else
+ self:error500( client, "No suitable path handler found" )
+ end
+ else
+ self:error500( client, "No suitable host handler found" )
end
else
self:error400( client, err )
+ return nil
end
-
- -- send response
- self:error400( client, "Dummy response" )
end