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