f471e6b0cc63bf6501d93b0d98f657eaa7fbce1d
[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.isdead(self)
65         return coroutine.status(self.routine) == "dead"
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.remove_dead(self, thread)
90         if self.debug then
91                 self:dprint("Completed " .. tostring(thread))
92         end
93         thread.socket:close()
94         self.threadc = self.threadc - 1
95         self.threads[thread.socket] = nil
96 end
97
98 function Daemon.kill_timedout(self)
99         local now = os.time()
100         
101         for sock, thread in pairs(self.threads) do
102                 if os.difftime(now, thread:touched()) > self.timeout then
103                         self.threads[sock] = nil
104                         self.threadc = self.threadc - 1
105                         sock:close()
106                 end
107         end
108 end
109
110 function Daemon.dprint(self, msg)
111         if self.debug then
112                 io.stderr:write("[daemon] " .. msg .. "\n")
113         end
114 end
115
116 function Daemon.register(self, sock, clhandler, errhandler)
117         table.insert( self.reading, sock )
118         self.handler[sock] = { clhandler = clhandler, errhandler = errhandler }
119 end
120
121 function Daemon.run(self)
122         while true do
123                 self:step()
124         end
125 end
126
127 function Daemon.step(self)      
128         local input, output, err = socket.select( self.reading, nil, 0 )
129         local working = false
130
131         -- accept new connections
132         for i, connection in ipairs(input) do
133
134                 local sock = connection:accept()
135                 
136                 if sock then
137                         -- check capacity
138                         if not self.threadlimit or self.threadc < self.threadlimit then
139                                 
140                                 if self.debug then
141                                         self:dprint("Accepted incoming connection from " .. sock:getpeername())
142                                 end
143                                 
144                                 local t = Thread(sock, self.handler[connection].clhandler)
145                                 self.threads[sock] = t
146                                 self.threadc = self.threadc + 1
147         
148                                 if self.debug then
149                                         self:dprint("Created " .. tostring(t))
150                                 end
151         
152                         -- reject client
153                         else
154                                 self:kill_timedout()
155                         
156                                 if self.debug then
157                                         self:dprint("Rejected incoming connection from " .. sock:getpeername())
158                                 end
159         
160                                 if self.handler[connection].errhandler then
161                                         self.handler[connection].errhandler( sock )
162                                 end
163         
164                                 sock:close()
165                         end
166                 end
167         end
168
169         -- create client handler
170         for sock, thread in pairs( self.threads ) do            
171                 -- resume working threads
172                 if not thread:iswaiting() then
173                         if self.debug then
174                                 self:dprint("Resuming " .. tostring(thread))
175                         end
176
177                         local stat, err = thread:resume()
178                         if stat and not thread:isdead() then
179                                 thread:touch()
180                                 if not thread:iswaiting() then
181                                         working = true
182                                 else
183                                         table.insert(self.waiting, sock)
184                                 end
185                         else
186                                 self:remove_dead(thread)
187                         end
188                         
189                         if self.debug then
190                                 self:dprint(tostring(thread) .. " returned")
191                                 if not stat then
192                                         self:dprint("Error in " .. tostring(thread) .. " " .. err)
193                                 end
194                         end
195                 end
196         end
197         
198         -- check for data on waiting threads
199         input, output, err = socket.select( self.waiting, nil, 0 )
200         
201         for i, sock in ipairs(input) do 
202                 local thread = self.threads[sock]
203                 thread:resume()
204                 if thread:isdead() then
205                         self:remove_dead(thread)
206                 else
207                         thread:touch()
208                         
209                         if not thread:iswaiting() then
210                                 for i, s in ipairs(self.waiting) do
211                                         if s == sock then
212                                                 table.remove(self.waiting, i)
213                                                 break
214                                         end
215                                 end
216                                 if not working then
217                                         working = true
218                                 end
219                         end
220                 end
221         end
222         
223         if err == "timeout" and not working then
224                 self:kill_timedout()
225                 socket.sleep(self.waittime)
226         end
227 end