]]--
module("luci.httpd.server", package.seeall)
+require("socket")
+require("socket.http")
require("luci.util")
READ_BUFSIZE = 1024
+
VHost = luci.util.class()
function VHost.__init__(self, handler)
self.dhandler = {}
end
-function VHost.process(self, ...)
- -- TODO: Dispatch handler
-end
-
-function VHost.sethandler(self, handler, match)
- if match then
- self.dhandler[match] = handler
- else
- self.handler = handler
- end
-end
+function VHost.process(self, request, sourcein, sinkerr, ...)
+ local handler = self.handler
+ local uri = request.env.REQUEST_URI:match("^([^?]*)")
+ -- SCRIPT_NAME
+ request.env.SCRIPT_NAME = ""
-Server = luci.util.class()
+ -- Call URI part
+ request.env.PATH_INFO = uri
-function Server.__init__(self, ip, port, base)
- self.socket = socket.bind(ip, port)
- self.socket:settimeout(0, "t")
- self.clhandler = client_handler
- self.errhandler = error503
- self.host = nil
- self.vhosts = {}
-
- -- Clone another server
- if base then
- getmetatable(self).__index = base
+ for k, dhandler in pairs(self.dhandler) do
+ if k == uri or k.."/" == uri:sub(1, #k+1) then
+ handler = dhandler
+ request.env.SCRIPT_NAME = k
+ request.env.PATH_INFO = uri:sub(#k+1)
+ break;
+ end
end
-end
--- Sets a vhost
-function Server.setvhost(self, vhost, name)
- if name then
- self.vhosts[name] = vhost
- else
- self.host = vhost
+ if handler then
+ return handler:process(request, sourcein, sinkerr, ...)
end
end
-function Server.error400(self, client, msg)
- client:send( "HTTP/1.0 400 Bad request\r\n" )
- client:send( "Content-Type: text/plain\r\n\r\n" )
+function VHost.set_default_handler(self, handler)
+ self.handler = handler
+end
- if msg then
- client:send( msg .. "\r\n" )
- end
- client:close()
+function VHost.set_handler(self, match, handler)
+ self.dhandler[match] = handler
end
-function Server.error503(self, client)
- client:send( "HTTP/1.0 503 Server unavailable\r\n" )
- client:send( "Content-Type: text/plain\r\n\r\n" )
- client:send( "There are too many clients connected, try again later\r\n" )
-end
-function Server.process(self, ...)
- -- TODO: Dispatch vhost
-end
+Server = luci.util.class()
-function Server.client_handler(self, client)
+function Server.__init__(self, host)
+ self.host = host
+ self.vhosts = {}
+end
- client:settimeout( 0 )
+function Server.set_default_vhost(self, vhost)
+ self.host = vhost
+end
- -- Create LTN12 block source
- local block_source = function()
+-- Sets a vhost
+function Server.set_vhost(self, name, vhost)
+ self.vhosts[name] = vhost
+end
- coroutine.yield()
+function Server.create_daemon_handlers(self)
+ return function(...) return self:process(...) end,
+ function(...) return self:error_overload(...) end
+end
- local chunk, err, part = client:receive( READ_BUFSIZE )
- if chunk == nil and err == "timeout" then
- return part
- elseif chunk ~= nil then
- return chunk
- else
- return nil, err
- end
+function Server.error(self, socket, code, msg)
+ hcode = tostring(code)
+
+ socket:send( "HTTP/1.0 " .. hcode .. " " ..
+ luci.http.protocol.statusmsg[code] .. "\r\n" )
+ socket:send( "Connection: close\r\n" )
+ socket:send( "Content-Type: text/plain\r\n\r\n" )
+ if msg then
+ socket:send( "HTTP-Error " .. code .. ": " .. msg .. "\r\n" )
end
+end
- -- Create LTN12 line source
- local line_source = ltn12.source.simplify( function()
-
- coroutine.yield()
+function Server.error_overload(self, socket)
+ self:error(socket, 503, "Too many simultaneous connections")
+end
- local chunk, err, part = client:receive("*l")
- -- Line too long
- if chunk == nil and err ~= "timeout" then
+function Server.process( self, thread )
- return nil, part
- and "Line exceeds maximum allowed length["..part.."]"
- or "Unexpected EOF"
+ -- Setup sockets and sources
+ local client = thread.socket
+
+ client:settimeout( 0 )
+
+ local sourcein = ltn12.source.empty()
+ local sourcehdr = luci.http.protocol.header_source( thread )
+ local sinkerr = ltn12.sink.file( io.stderr )
+
+ local close = false
+
+ local reading = { client }
- -- Line ok
+ local message, err
+
+ repeat
+ -- parse headers
+ message, err = luci.http.protocol.parse_message_header( sourcehdr )
+
+ if not message then
+ self:error( client, 400, err )
+ break
+ end
+
+ -- keep-alive
+ if message.http_version == 1.1 then
+ close = (message.env.HTTP_CONNECTION == "close")
else
-
- -- Strip trailing CR
- chunk = chunk:gsub("\r$","")
-
- -- We got end of headers, switch to dummy source
- if #chunk == 0 then
- return "", function()
- return nil
- end
+ close = not message.env.HTTP_CONNECTION or message.env.HTTP_CONNECTION == "close"
+ end
+
+ if message.request_method == "get" or message.request_method == "head" then
+ -- Be happy
+
+ elseif message.request_method == "post" then
+ -- If we have a HTTP/1.1 client and an Expect: 100-continue header then
+ -- respond with HTTP 100 Continue message
+ if message.http_version == 1.1 and message.headers['Expect'] and
+ message.headers['Expect'] == '100-continue'
+ then
+ client:send("HTTP/1.1 100 Continue\r\n\r\n")
+ end
+
+ if message.headers['Transfer-Encoding'] and
+ message.headers['Transfer-Encoding'] ~= "identity" then
+ sourcein = socket.source("http-chunked", thread)
+ elseif message.env.CONTENT_LENGTH then
+ sourcein = socket.source("by-length", thread,
+ tonumber(message.env.CONTENT_LENGTH))
else
- return chunk, nil
+ self:error( client, 411, luci.http.protocol.statusmsg[411] )
+ break;
end
+
+ else
+ self:error( client, 405, luci.http.protocol.statusmsg[405] )
+ break;
+
end
- end )
-
- coroutine.yield(client)
-
- -- parse message
- local message, err = luci.http.protocol.parse_message_header( line_source )
- if message then
-
- -- If we have a HTTP/1.1 client and an Expect: 100-continue header then
- -- respond with HTTP 100 Continue message
- if message.http_version == 1.1 and message.headers['Expect'] and
- message.headers['Expect'] == '100-continue'
- then
- client:send("HTTP/1.1 100 Continue\r\n\r\n")
+ local host = self.vhosts[message.env.HTTP_HOST] or self.host
+ if not host then
+ self:error( client, 500, "Unable to find matching host" )
+ break;
end
-
-
- local s, e = luci.http.protocol.parse_message_body( block_source, message )
-
- -- XXX: debug
- luci.util.dumptable( message )
-
- if not s and e then
- self:error400( client, e )
+
+ local response, sourceout = host:process(
+ message, sourcein, sinkerr,
+ client, io.stderr
+ )
+ if not response then
+ self:error( client, 500, "Error processing handler" )
end
- else
- self:error400( client, err )
- end
-
- -- send response
- self:error400( client, "Dummy response" )
+
+ -- Post process response
+ local sinkmode = close and "close-when-done" or "keep-open"
+
+ if sourceout then
+ if not response.headers["Content-Length"] then
+ if message.http_version == 1.1 then
+ response.headers["Transfer-Encoding"] = "chunked"
+ sinkmode = "http-chunked"
+ else
+ close = true
+ sinkmode = "close-when-done"
+ end
+ end
+ end
+
+ if close then
+ response.headers["Connection"] = "close"
+ end
+
+
+ local sinkout = socket.sink(sinkmode, client)
+
+ local header =
+ message.env.SERVER_PROTOCOL .. " " ..
+ tostring(response.status) .. " " ..
+ luci.http.protocol.statusmsg[response.status] .. "\r\n"
+
+
+ for k,v in pairs(response.headers) do
+ header = header .. k .. ": " .. v .. "\r\n"
+ end
+
+ client:send(header .. "\r\n")
+
+ if sourceout then
+ local eof = false
+ repeat
+ coroutine.yield()
+ eof = not ltn12.pump.step(sourceout, sinkout)
+ until eof
+ end
+ until close
+
+ client:close()
end