X-Git-Url: https://git.archive.openwrt.org/?p=project%2Fluci.git;a=blobdiff_plain;f=libs%2Fhttpd%2Fluasrc%2Fhttpd%2Fserver.lua;h=57221647fceba2893a98e255a601a00f4c0f81d3;hp=7f973ac03cce72e73cdd2f40726c48df454e40c8;hb=741bbe7817d94a02270e3bf425b6e0ff77c5015d;hpb=90aef16aea255b6f863cda36f118bf5bde0316b4 diff --git a/libs/httpd/luasrc/httpd/server.lua b/libs/httpd/luasrc/httpd/server.lua index 7f973ac03..57221647f 100644 --- a/libs/httpd/luasrc/httpd/server.lua +++ b/libs/httpd/luasrc/httpd/server.lua @@ -14,10 +14,13 @@ $Id$ ]]-- 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) @@ -25,149 +28,200 @@ 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