summaryrefslogtreecommitdiffhomepage
path: root/libs/httpd/luasrc/httpd.lua
blob: 050e03ec2b10b682cc2046bfe6ae10d065bf1cbf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
--[[

HTTP server implementation for LuCI - core
(c) 2008 Freifunk Leipzig / Jo-Philipp Wich <xm@leipzig.freifunk.net>

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("socket")
require("luci.util")

function Socket(ip, port)
	local sock, err = socket.bind( ip, port )

	if sock then
		sock:settimeout( 0, "t" )
	end

	return sock, err
end

Thread = luci.util.class()

function Thread.__init__(self, socket, func)
	self.socket  = socket
	self.routine = coroutine.create(func)
	self.stamp   = os.time()
	self.waiting = false
end

function Thread.touched(self)
	return self.stamp
end

function Thread.iswaiting(self)
	return self.waiting
end

function Thread.receive(self, ...)
	local chunk, err, part
	self.waiting = true
	
	repeat
		coroutine.yield()
		chunk, err, part = self.socket:receive(...)
	until err ~= "timeout"
	
	self.waiting = false
	return chunk, err, part
end

function Thread.resume(self, ...)
	return coroutine.resume(self.routine, self, ...)
end

function Thread.isdead(self)
	return coroutine.status(self.routine) == "dead"
end

function Thread.touch(self)
	self.stamp = os.time()
end

Daemon = luci.util.class()

function Daemon.__init__(self, threadlimit, waittime, timeout)
	self.reading = {}
	self.threads = {}
	self.handler = {}
	self.waiting = {}
	self.threadc = 0
	
	setmetatable(self.waiting, {__mode = "v"})
	
	self.debug   = false
	self.threadlimit = threadlimit
	self.waittime = waittime or 0.1
	self.timeout  = timeout or 90
end

function Daemon.remove_dead(self, thread)
	if self.debug then
		self:dprint("Completed " .. tostring(thread))
	end
	thread.socket:close()
	self.threadc = self.threadc - 1
	self.threads[thread.socket] = nil
end

function Daemon.kill_timedout(self)
	local now = os.time()
	
	for k, v in pairs(self.threads) do
		if os.difftime(now, thread:touched()) > self.timeout then
			self.threads[sock] = nil
			self.threadc = self.threadc - 1
			sock:close()
		end
	end
end

function Daemon.dprint(self, msg)
	if self.debug then
		io.stderr:write("[daemon] " .. msg .. "\n")
	end
end

function Daemon.register(self, sock, clhandler, errhandler)
	table.insert( self.reading, sock )
	self.handler[sock] = { clhandler = clhandler, errhandler = errhandler }
end

function Daemon.run(self)
	while true do
		self:step()
	end
end

function Daemon.step(self)	
	local input, output, err = socket.select( self.reading, nil, 0 )
	local working = false

	-- accept new connections
	for i, connection in ipairs(input) do

		local sock = connection:accept()
		
		if sock then
			-- check capacity
			if not self.threadlimit or self.threadc < self.threadlimit then
				
				if self.debug then
					self:dprint("Accepted incoming connection from " .. sock:getpeername())
				end
				
				local t = Thread(sock, self.handler[connection].clhandler)
				self.threads[sock] = t
				self.threadc = self.threadc + 1
	
				if self.debug then
					self:dprint("Created " .. tostring(t))
				end
	
			-- reject client
			else
				self:kill_timedout()
			
				if self.debug then
					self:dprint("Rejected incoming connection from " .. sock:getpeername())
				end
	
				if self.handler[connection].errhandler then
					self.handler[connection].errhandler( sock )
				end
	
				sock:close()
			end
		end
	end

	-- create client handler
	for sock, thread in pairs( self.threads ) do		
		-- resume working threads
		if not thread:iswaiting() then
			if self.debug then
				self:dprint("Resuming " .. tostring(thread))
			end

			local stat, err = thread:resume()
			if stat and not thread:isdead() then
				thread:touch()
				if not thread:iswaiting() then
					working = true
				else
					table.insert(self.waiting, sock)
				end
			else
				self:remove_dead(thread)
			end
			
			if self.debug then
				self:dprint(tostring(thread) .. " returned")
				if not stat then
					self:dprint("Error in " .. tostring(thread) .. " " .. err)
				end
			end
		end
	end
	
	-- check for data on waiting threads
	input, output, err = socket.select( self.waiting, nil, 0 )
	
	for i, sock in ipairs(input) do	
		local thread = self.threads[sock]
		thread:resume()
		if thread:isdead() then
			self:remove_dead(thread)
		else
			thread:touch()
			
			if not thread:iswaiting() then
				for i, s in ipairs(self.waiting) do
					if s == sock then
						table.remove(self.waiting, i)
						break
					end
				end
				if not working then
					working = true
				end
			end
		end
	end
	
	if err == "timeout" and not working then
		self:kill_timedout()
		socket.sleep(self.waittime)
	end
end