--[[
-LuCI - HTTPD
+
+HTTP server implementation for LuCI - core
+(c) 2008 Freifunk Leipzig / Jo-Philipp Wich <xm@leipzig.freifunk.net>
+(c) 2008 Steven Barth <steven@midlink.org>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+$Id$
+
]]--
+
module("luci.httpd", package.seeall)
-require("luci.copas")
-require("luci.http.protocol")
-require("luci.sys")
+require("socket")
+THREAD_IDLEWAIT = 0.01
+THREAD_TIMEOUT = 90
+THREAD_LIMIT = nil
+local reading = {}
+local clhandler = {}
+local erhandler = {}
-function run(config)
- -- TODO: process config
- local server = socket.bind("0.0.0.0", 8080)
- copas.addserver(server, spawnworker)
-
- while true do
- copas.step()
- end
-end
+local threadc = 0
+local threads = {}
+local threadm = {}
+local threadi = {}
+local _meta = {__mode = "k"}
+setmetatable(threadm, _meta)
+setmetatable(threadi, _meta)
-function spawnworker(socket)
- socket = copas.wrap(socket)
- local request = luci.http.protocol.parse_message_header(socket)
- request.input = socket -- TODO: replace with streamreader
- request.error = io.stderr
-
-
- local output = socket -- TODO: replace with streamwriter
-
- -- TODO: detect matching handler
- local h = luci.httpd.FileHandler.SimpleHandler(luci.sys.libpath() .. "/httpd/httest")
- h:process(request, output)
-end
+function Socket(ip, port)
+ local sock, err = socket.bind( ip, port )
-Response = luci.util.class()
-function Response.__init__(self, sourceout, headers, status)
- self.sourceout = sourceout or function() end
- self.headers = headers or {}
- self.status = status or 200
-end
+ if sock then
+ sock:settimeout( 0, "t" )
+ end
-function Response.addheader(self, key, value)
- self.headers[key] = value
+ return sock, err
end
-function Response.setstatus(self, status)
- self.status = status
-end
+function corecv(socket, ...)
+ threadi[socket] = true
-function Response.setsource(self, source)
- self.sourceout = source
+ while true do
+ local chunk, err, part = socket:receive(...)
+
+ if err ~= "timeout" then
+ threadi[socket] = false
+ return chunk, err, part
+ end
+
+ coroutine.yield()
+ end
end
+function cosend(socket, chunk, i, ...)
+ threadi[socket] = true
+ i = i or 1
-Handler = luci.util.class()
-function Handler.__init__(self)
- self.filter = {}
+ while true do
+ local stat, err, sent = socket:send(chunk, i, ...)
+
+ if err ~= "timeout" then
+ threadi[socket] = false
+ return stat, err, sent
+ else
+ i = sent and (sent + 1) or i
+ end
+
+ coroutine.yield()
+ end
end
-function Handler.addfilter(self, filter)
- table.insert(self.filter, filter)
+function register(socket, s_clhandler, s_errhandler)
+ table.insert(reading, socket)
+ clhandler[socket] = s_clhandler
+ erhandler[socket] = s_errhandler
end
-function Handler.process(self, request, output)
- -- TODO: Process input filters
+function run()
+ while true do
+ step()
+ end
+end
- local response = self:handle(request)
-
- -- TODO: Process output filters
+function step()
+ local idle = true
+ if not THREAD_LIMIT or threadc < THREAD_LIMIT then
+ local now = os.time()
+ for i, server in ipairs(reading) do
+ local client = server:accept()
+ if client then
+ threadm[client] = now
+ threadc = threadc + 1
+ threads[client] = coroutine.create(clhandler[server])
+ end
+ end
+ end
- output:send("HTTP/1.0 " .. response.status .. " BLA\r\n")
- for k, v in pairs(response.headers) do
- output:send(k .. ": " .. v .. "\r\n")
+ for client, thread in pairs(threads) do
+ coroutine.resume(thread, client)
+ local now = os.time()
+ if coroutine.status(thread) == "dead" then
+ threadc = threadc - 1
+ threads[client] = nil
+ elseif threadm[client] and threadm[client] + THREAD_TIMEOUT < now then
+ threads[client] = nil
+ threadc = threadc - 1
+ client:close()
+ elseif not threadi[client] then
+ threadm[client] = now
+ idle = false
+ end
end
- output:send("\r\n")
-
- for chunk in response.sourceout do
- output:send(chunk)
+ if idle then
+ collectgarbage()
+ socket.sleep(THREAD_IDLEWAIT)
end
end
-