9eaa9a68dcc0da76738979ed3e92ca44791ff9dc
[project/luci.git] / libs / httpd / luasrc / httpd.lua
1 --[[
2
3 HTTP server implementation for LuCI - core
4 (c) 2008 Freifunk Leipzig / Jo-Philipp Wich <xm@leipzig.freifunk.net>
5
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
9
10         http://www.apache.org/licenses/LICENSE-2.0
11
12 $Id$
13
14 ]]--
15
16 module("luci.httpd", package.seeall)
17 require("socket")
18 require("luci.util")
19
20 function Socket(ip, port)
21         local sock, err = socket.bind( ip, port )
22
23         if sock then
24                 sock:settimeout( 0, "t" )
25         end
26
27         return sock, err
28 end
29
30 Thread = luci.util.class()
31
32 function Thread.__init__(self, socket, func)
33         self.socket  = socket
34         self.routine = coroutine.create(func)
35         self.stamp   = os.time()
36         self.waiting = false
37 end
38
39 function Thread.touched(self)
40         return self.stamp
41 end
42
43 function Thread.iswaiting(self)
44         return self.waiting
45 end
46
47 function Thread.receive(self, ...)
48         local chunk, err, part
49         self.waiting = true
50         
51         repeat
52                 coroutine.yield()
53                 chunk, err, part = self.socket:receive(...)
54         until err ~= "timeout"
55         
56         self.waiting = false
57         return chunk, err, part
58 end
59
60 function Thread.resume(self, ...)
61         return coroutine.resume(self.routine, self, ...)
62 end
63
64 function Thread.status(self)
65         return coroutine.status(self.routine)
66 end
67
68 function Thread.touch(self)
69         self.stamp = os.time()
70 end
71
72 Daemon = luci.util.class()
73
74 function Daemon.__init__(self, threadlimit, waittime, timeout)
75         self.reading = {}
76         self.threads = {}
77         self.handler = {}
78         self.waiting = {}
79         self.threadc = 0
80         
81         setmetatable(self.waiting, {__mode = "v"})
82         
83         self.debug   = false
84         self.threadlimit = threadlimit
85         self.waittime = waittime or 0.1
86         self.timeout  = timeout or 90
87 end
88
89 function Daemon.dprint(self, msg)
90         if self.debug then
91                 io.stderr:write("[daemon] " .. msg .. "\n")
92         end
93 end
94
95 function Daemon.register(self, sock, clhandler, errhandler)
96         table.insert( self.reading, sock )
97         self.handler[sock] = { clhandler = clhandler, errhandler = errhandler }
98 end
99
100 function Daemon.run(self)
101         while true do
102                 self:step()
103         end
104 end
105
106 function Daemon.step(self)      
107         local input, output, err = socket.select( self.reading, nil, 0 )
108         local working = false
109
110         -- accept new connections
111         for i, connection in ipairs(input) do
112
113                 local sock = connection:accept()
114                 
115                 if sock then
116                         -- check capacity
117                         if not self.threadlimit or self.threadc < self.threadlimit then
118                                 
119                                 if self.debug then
120                                         self:dprint("Accepted incoming connection from " .. sock:getpeername())
121                                 end
122                                 
123                                 local t = Thread(sock, self.handler[connection].clhandler)
124                                 self.threads[sock] = t
125                                 self.threadc = self.threadc + 1
126         
127                                 if self.debug then
128                                         self:dprint("Created " .. tostring(t))
129                                 end
130         
131                         -- reject client
132                         else
133                                 if self.debug then
134                                         self:dprint("Rejected incoming connection from " .. sock:getpeername())
135                                 end
136         
137                                 if self.handler[connection].errhandler then
138                                         self.handler[connection].errhandler( sock )
139                                 end
140         
141                                 sock:close()
142                         end
143                 end
144         end
145
146         -- create client handler
147         for sock, thread in pairs( self.threads ) do
148                 local now = os.time()
149
150                 -- reap dead clients
151                 if thread:status() == "dead" then
152                         if self.debug then
153                                 self:dprint("Completed " .. tostring(thread))
154                         end
155                         sock:close()
156                         self.threadc = self.threadc - 1
157                         self.threads[sock] = nil
158                         
159                 elseif os.difftime(now, thread:touched()) > self.timeout then
160                         self.threads[sock] = nil
161                         sock:close()
162                 -- resume working threads
163                 elseif not thread:iswaiting() then
164                         if self.debug then
165                                 self:dprint("Resuming " .. tostring(thread))
166                         end
167
168                         local stat, err = thread:resume()
169                         if stat then
170                                 thread:touch()
171                                 if not thread:iswaiting() then
172                                         working = true
173                                 else
174                                         table.insert(self.waiting, sock)
175                                 end
176                         end
177                         
178                         if self.debug then
179                                 self:dprint(tostring(thread) .. " returned")
180                                 if not stat then
181                                         self:dprint("Error in " .. tostring(thread) .. " " .. err)
182                                 end
183                         end
184                 end
185         end
186         
187         -- check for data on waiting threads
188         input, output, err = socket.select( self.waiting, nil, 0 )
189         
190         for i, sock in ipairs(input) do         
191                 self.threads[sock]:resume()
192                 self.threads[sock]:touch()
193                 
194                 if not self.threads[sock]:iswaiting() then
195                         for i, s in ipairs(self.waiting) do
196                                 if s == sock then
197                                         table.remove(self.waiting, i)
198                                         break
199                                 end
200                         end
201                         if not working then
202                                 working = true
203                         end
204                 end
205         end
206         
207         if err == "timeout" and not working then
208                 socket.sleep(self.waittime)
209         end
210 end