* libs/httpd: Fixed garbage collection
[project/luci.git] / libs / httpd / luasrc / httpd.lua
index 773d3c873dfd821dae28655a246eba7d8d83c5c1..541063cc7e21cf16c6132076d75e76fb868b3615 100644 (file)
 --[[
-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
-