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