* libs/httpd: Rewrote daemon controller to increase performance
[project/luci.git] / libs / httpd / luasrc / httpd / server.lua
1 --[[
2
3 HTTP server implementation for LuCI - helper class
4 (c) 2008 Freifunk Leipzig / Jo-Philipp Wich <xm@leipzig.freifunk.net>
5 (c) 2008 Steven Barth <steven@midlink.org>
6
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
10
11         http://www.apache.org/licenses/LICENSE-2.0
12
13 $Id$
14
15 ]]--
16
17 module("luci.httpd.server", package.seeall)
18 require("socket")
19 require("socket.http")
20 require("luci.util")
21
22 READ_BUFSIZE = 1024
23
24
25 VHost = luci.util.class()
26
27 function VHost.__init__(self, handler)
28         self.handler = handler
29         self.dhandler = {}
30 end
31
32 function VHost.process(self, request, sourcein, sinkerr, ...)
33         local handler = self.handler
34
35         local uri = request.env.REQUEST_URI:match("^([^?]*)")
36
37         -- SCRIPT_NAME
38         request.env.SCRIPT_NAME = ""
39
40         -- Call URI part
41         request.env.PATH_INFO = uri
42
43         for k, dhandler in pairs(self.dhandler) do
44                 if k == uri or k.."/" == uri:sub(1, #k+1) then
45                         handler = dhandler
46                         request.env.SCRIPT_NAME = k
47                         request.env.PATH_INFO   = uri:sub(#k+1)
48                         break;
49                 end
50         end
51
52         if handler then
53                 return handler:process(request, sourcein, sinkerr, ...)
54         end
55 end
56
57
58 function VHost.set_default_handler(self, handler)
59         self.handler = handler
60 end
61
62
63 function VHost.set_handler(self, match, handler)
64         self.dhandler[match] = handler
65 end
66
67
68
69 Server = luci.util.class()
70
71 function Server.__init__(self, host)
72         self.host = host
73         self.vhosts = {}
74 end
75
76 function Server.set_default_vhost(self, vhost)
77         self.host = vhost
78 end
79
80 -- Sets a vhost
81 function Server.set_vhost(self, name, vhost)
82         self.vhosts[name] = vhost
83 end
84
85 function Server.create_daemon_handlers(self)
86         return function(...) return self:process(...) end,
87                 function(...) return self:error_overload(...) end
88 end
89
90
91 function Server.error(self, socket, code, msg)
92         hcode = tostring(code)
93         
94         socket:send( "HTTP/1.0 " .. hcode .. " " ..
95          luci.http.protocol.statusmsg[code] .. "\r\n" )
96         socket:send( "Connection: close\r\n" )
97         socket:send( "Content-Type: text/plain\r\n\r\n" )
98
99         if msg then
100                 socket:send( "HTTP-Error " .. code .. ": " .. msg .. "\r\n" )
101         end
102 end
103
104 function Server.error_overload(self, socket)
105         self:error(socket, 503, "Too many simultaneous connections")
106 end
107
108
109 function Server.process( self, client )
110
111         -- Setup sockets and sources
112         local thread = {
113                 receive = function(self, ...) return luci.httpd.corecv(client, ...) end
114         }
115         
116         client:settimeout( 0 )
117         
118         local sourcein  = ltn12.source.empty()
119         local sourcehdr = luci.http.protocol.header_source( thread )
120         local sinkerr   = ltn12.sink.file( io.stderr )
121         
122         local close = false
123         
124         local reading = { client }
125
126         local message, err
127         
128         repeat
129                 -- parse headers
130                 message, err = luci.http.protocol.parse_message_header( sourcehdr )
131
132                 if not message then
133                         self:error( client, 400, err )
134                         break
135                 end     
136                 
137                 -- keep-alive
138                 if message.http_version == 1.1 then
139                         close = (message.env.HTTP_CONNECTION == "close")
140                 else
141                         close = not message.env.HTTP_CONNECTION or message.env.HTTP_CONNECTION == "close"
142                 end
143         
144                 if message.request_method == "get" or message.request_method == "head" then
145                         -- Be happy
146                         
147                 elseif message.request_method == "post" then
148                         -- If we have a HTTP/1.1 client and an Expect: 100-continue header then
149                         -- respond with HTTP 100 Continue message
150                         if message.http_version == 1.1 and message.headers['Expect'] and
151                                 message.headers['Expect'] == '100-continue'
152                         then
153                                 client:send("HTTP/1.1 100 Continue\r\n\r\n")
154                         end
155                         
156                         if message.headers['Transfer-Encoding'] and
157                          message.headers['Transfer-Encoding'] ~= "identity" then
158                                 sourcein = socket.source("http-chunked", thread)
159                         elseif message.env.CONTENT_LENGTH then
160                                 sourcein = socket.source("by-length", thread,
161                                  tonumber(message.env.CONTENT_LENGTH))
162                         else
163                                 self:error( client, 411, luci.http.protocol.statusmsg[411] )
164                                 break;
165                         end
166                         
167                 else
168                         self:error( client, 405, luci.http.protocol.statusmsg[405] )
169                         break;
170                         
171                 end
172
173
174                 local host = self.vhosts[message.env.HTTP_HOST] or self.host
175                 if not host then
176                         self:error( client, 500, "Unable to find matching host" )
177                         break;
178                 end
179                 
180                 local response, sourceout = host:process(
181                         message, sourcein, sinkerr,
182                         client, io.stderr 
183                 )
184                 if not response then
185                         self:error( client, 500, "Error processing handler" )
186                 end
187                 
188                 -- Post process response
189                 local sinkmode = close and "close-when-done" or "keep-open"
190                 
191                 if sourceout then
192                         if not response.headers["Content-Length"] then
193                                 if message.http_version == 1.1 then
194                                         response.headers["Transfer-Encoding"] = "chunked"
195                                         sinkmode = "http-chunked"
196                                 else
197                                         close = true
198                                         sinkmode = "close-when-done"
199                                 end
200                         end
201                 end
202                 
203                 if close then
204                         response.headers["Connection"] = "close"
205                 end
206                 
207                 
208                 local sinkout = socket.sink(sinkmode, client)
209                 
210                 local header =
211                         message.env.SERVER_PROTOCOL .. " " ..
212                         tostring(response.status) .. " " ..
213                         luci.http.protocol.statusmsg[response.status] .. "\r\n"
214
215                 
216                 for k,v in pairs(response.headers) do
217                         header = header .. k .. ": " .. v .. "\r\n"
218                 end
219                 
220                 client:send(header .. "\r\n")
221                 
222                 if sourceout then
223                         local eof = false
224                         repeat
225                                 coroutine.yield()
226                                 eof = not ltn12.pump.step(sourceout, sinkout)
227                         until eof
228                 end
229         until close
230         
231         client:close()
232 end