-- -*-mode: C++; style: K&R; c-basic-offset: 4 ; -*- */ -- -- A simple HTTP server written in Lua, using the socket primitives -- in 'libhttpd.so'. -- -- This server will handle multiple virtual hosts. The only requirement -- is that each virtual host must have the documents located beneath -- a common tree. Note that directory indexes are not (yet) supported. -- -- For example to serve the three virtual hosts: -- lappy -- bob -- localhost -- -- You should have a directory layout such as: -- -- ./vhosts/ -- -- ./vhosts/bob/ -- ./vhosts/bob/htdocs/ -- ./vhosts/bob/htdocs/index.html [etc] -- ./vhosts/bob/logs/ -- -- ./vhosts/lappy/ -- ./vhosts/lappy/htdocs/ -- ./vhosts/lappy/htdocs/index.html [etc] -- ./vhosts/lappy/logs/ -- -- ./vhosts/localhost/ -- ./vhosts/localhost/htdocs/ -- ./vhosts/localhost/htdocs/index.html [etc] -- ./vhosts/localhost/logs/ -- -- -- (If you wish to allow for fully qualified hosts simply add symbolic -- links to suit your hostnames.) -- -- -- If a request arrives for a virtual host which you are not serving -- then it will be passed to the faux server "default". Simply create -- a symbolic link to the server you wish to be your default host: -- -- cd ./vhosts -- ln -s lappy default -- -- -- -- The code is NOT secure -- ---------------------- -- -- By that I mean that I won't promise any security. I take the obvious -- step of filtering '/../' from incoming requests and Hostnames, but there -- may be other weaknesses. -- -- Error messages are escaped by default, to prevent XSS attacks. -- -- DOS attacks might be possible, although again the obvious cases -- are covered. -- -- -- Steve Kemp -- -- -- http://www.steve.org.uk/ -- -- $Id: httpd.lua,v 1.31 2005/10/31 17:08:22 steve Exp $ -- -- load the socket library -- socket = require( "libhttpd" ); print( "\n\nLoaded the socket library, version: \n " .. socket.version ); -- -- A table of MIME types. -- These values will be loaded from /etc/mime.types if available, otherwise -- a minimum set of defaults will be used. -- mime = {}; -- -- Start a server upon the given port, using the given -- root directory path. -- -- The root path is designed to contain sub-directories for -- each given virtual host. -- function start_server( port, root ) running = 1; -- -- Bind a socket to the given port -- local listener = socket.bind( port ); if ( listener == nil ) then error("Error in bind()"); end -- -- Print some status messages. -- print( "\nListening upon:" ); print( " http://localhost:" .. port .. "/" ); print( "\nLoading virtual hosts from beneath: " ); print( " " .. root .. "\n\n"); -- -- Loop accepting requests. -- while ( running == 1 ) do processConnection( root, listener ); end -- -- Finished. -- socket.close( listener ); end -- -- Process a single incoming connection. -- function processConnection( root, listener ) -- -- Accept a new connection -- client,ip = socket.accept( listener ); found = 0; -- Found the end of the HTTP headers? chunk = 0; -- Count of data read from client socket size = 0; -- Total size of incoming request. code = 0; -- Status code we send to the client request = ""; -- Request body read from client. -- -- Read in a response from the client, terminating at the first -- '\r\n\r\n' line - this is the end of the HTTP header request. -- -- Also break out of this loop if we read ten packets of data -- from the client but didn't manage to find a HTTP header end. -- This should help protect us from DOSes. -- while ( ( found == 0 ) and ( chunk < 10 ) ) do length, data = socket.read(client); if ( length < 1 ) then found = 1; end size = size + length; request = request .. data; position,len = string.find( request, '\r\n\r\n' ); if ( position ~= nil ) then found = 1; end chunk = chunk + 1; end -- -- OK We now have a complete HTTP request of 'size' bytes long -- stored in 'request'. -- -- -- Find the requested path. -- _, _, method, path, major, minor = string.find(request, "([A-Z]+) (.+) HTTP/(%d).(%d)"); -- -- We only handle GET requests. -- if ( method ~= "GET" ) then error = "Method not implemented"; if ( method == nil ) then error = error .. "."; else error = error .. ": " .. urlEncode( method ); end size = sendError( client, 501, error ); socket.close( client ); return size, "501"; end -- -- Decode the requested path. -- path = urlDecode( path ); -- -- find the Virtual Host which we need for serving, and find the -- user agent and referer for logging purposes. -- _, _, host = string.find(request, "Host: ([^:\r\n]+)"); _, _, agent = string.find(request, "Agent: ([^\r\n]+)"); _, _, referer = string.find(request, "Referer: ([^\r\n]+)"); -- -- If there was no "Host:" header then use "default". -- if ( host == nil ) then host = "default"; end -- -- Is this a host that we're dealing with? -- -- For this code to work we would ideally use the "os.stat" patch -- against Lua 5.0 which can be found at: -- -- http://lua-users.org/wiki/PeterShook -- -- Instead we use our own "is_dir" function inside the socket -- library. -- info = socket.is_dir( root .. host ); if ( not info ) then host = 'default'; end -- -- If the request was for '/finish' then terminate ourselves -- if ( path == "/finish" ) then running = 0; socket.write( client, "HTTP/1.0 200 OK\r\n" ); socket.write( client, "Server: lua-httpd " .. socket.version .. "\r\n" ); socket.write( client, "Content-type: text/html\r\n" ); socket.write( client, "Connection: close\r\n\r\n" ); socket.write( client, "
As per your request.
"); else -- -- Otherwise attempt to handle the connection. -- size, code = handleRequest( root, host, path, client ); end if ( agent == nil ) then agent = "-" end; if ( referer == nil ) then referer = "-" end; if ( code == nill ) then code = 0 ; end; -- -- Log the access in something resembling the Apache common -- log format. -- -- Note: The logging does not write the name of the virtual host. -- logAccess( root, host, ip, path, code, size, agent, referer, major, minor ); if ( running == 0 ) then print( "Terminating as per request from " .. ip ); end -- -- Close the client connection. -- socket.close( client ); end -- -- Attempt to serve the given path to the given client -- function handleDirectory( client, path, request ) a = socket.readdir( path ); socket.write( client, "HTTP/1.0 200 OK\r\n" ); socket.write( client, "Server: lua-httpd " .. socket.version .. "\r\n" ); socket.write( client, "Content-type: text/html\r\n" ); socket.write( client, "Connection: close\r\n\r\n" ); msg = ""; msg = msg .. "