Using the package com.lukasfeiler.httpd.

See:
          Description

Packages
com.lukasfeiler.httpd The HTTP server daemon.
com.lukasfeiler.httpd.plugins Contains the plugins loadable with the configuration option Plugins.

 

Using the package com.lukasfeiler.httpd.


Table of Contents

  • Intended Use
  • Requirements
  • Starting the Server
  • Providing the service from within a firewall
  • The Configuration File
  • Main Options
  • Plugin Options
  • A Sample Configuration File
  • Writing your own plugins

  •  
     

    Intended Use

    com.lukasfeiler.httpd was originally developed to provide (streamed) mp3 files and m3u playlist files.
    It can be as well used as a simple server for static content. com.lukasfeiler.httpd implements only HTTP/1.0 and only the request method GET. KeepAlive is not supported.
     

    Requirements

    com.lukasfeiler.httpd requires J2SE v1.4.
     

    Starting the Server

    To start the HTTP server run the following command:
    java -jar com.lukasfeiler.httpd.jar <configuration_file>
     

    Providing the service from within a firewall

    To provide the service on the remote server example.com on the port 8080:
    You must have an SSH account on the remote server to make this work; Run the the following command on the machine com.lukasfeiler.httpd is running on:
    ssh -R 8079:localhost:80 user@example.com - this connects you to the remote server and forewards the remote port 8079 to the local port 80.

    Once connected you have to foreward the remote port 8080 to the remote port 8079 to make the service accessable for the outside world.
    ssh -gL 8080:localhost:8079 localhost

    Please keep in mind that now all requests will appear to come from localhost, so IPFilter won't be much of a use any more.
     

    The Configuration File

    - Every configuration option is placed on a seperate line.
    - Lines beginning with "#" are ignored.
      Example: # This is a comment
    - Multiple values for a single configuration option are separated by a single space.
      Example: Plugins AutoIndex ReadFile
    - A value that contains spaces must not be sourrounded by quotes.
      Example: HTTPAuthRealm This is a restricted area.
    - If a configuration option is used multiple times only the last one is considered.

    Please see A Sample Configuration File.
     

    Main Options

    The following options can be used in the configuration file and are always available - no matter which plugins are loaded.

     

    ListenPort

    With this configuration option you can specify on which local TCP port the server should listen on. The port must be between 0 and 65535, inclusive. A port number of 0 will cause the operating system to assign a port number. The default is port 80.
    Example:
    ListenPort 8080
     

    ListenAddress

    The local IP address the server will listen on. This can come in hany if you want the server to listen just on a specific interface on a multi-homed server. It can be used as well to limit the server to connections coming from localhost.
    If you use an IP address of 0.0.0.0 the server will listen on all available interfaces.
    The address can either be a machine name, such as "www.lukasfeiler.com" or "localhost", or a textual representation of its IP address. If you want to use a literal IPv6 address, either the form defined in RFC 2732 or the literal IPv6 address format defined in RFC 2373 is accepted. The default is localhost.
    Examples:
    ListenAddress 192.168.1.19
    ListenAddress localhost
    ListenAddress 0.0.0.0
     

    ConnectionTimeout

    If the specified number of milli-seconds pass without any traffic the connection will be closed. A timeout of zero is interpreted as an infinite timeout. The default is 10000 (10 seconds).
    Example:
    ConnectionTimeout 5000
     

    ConnectionLifetime

    The maximum lifetime of a connection in milli-seconds. When a new client connects all existing connections will be checked if they extended their lifetime. The default is 300000 (5 minutes).
    Example:
    ConnectionLifetime 150000
     

    ConnectionMaximum

    The maximum number of connections. In general if the maximum is reached no more connections are accepted (no SYN,ACK packets are sent on the TCP/IP level, no Socket objects are created) (please see ConnectionQueueLength and ConnectionMaximumDelay for details). The default is 20.
    Example:
    ConnectionMaximum 10
     

    ConnectionQueueLength

    The maximum queue length for incoming connection indications (TCP packets with only the SYN bit set). If a connection indication arrives when the queue is full, the connection is refused. ConnectionQueueLength must be a positive value greater than 0. If the value is equal or less than 0, then the operating system default will be used. Note: it is recommended to use the value 1 to enforce the ConnectionMaximum and ensure a better performace. A connection once in the queue will cause a new instance of HTTPSocket to be created. The HTTPSocket method run will then terminate the socket if the maximum was exceeded. The default value is 1.
    Example:
    ConnectionQueueLength 1
     

    ConnectionMaximumDelay

    After accepting a new connection it is checked whether the maximum number of connections was reached (no more connections should be accepted). If that is the case no more connections will be accepted for the number of milli-seconds specified with ConnectionMaximumDelay. This keeps new connection from ending up in the queue and causing new instances of HTTPSocket to be created and terminated immediately. The default value is 500 (half a second).
    Example:
    ConnectionMaximumDelay 100
     

    ServerBanner

    The value to send with the HTTP "Server: ..." header. For security reasons it is recommended to use a value that gives no hint to the fact that you are using com.lukasfeiler.httpd. The default is "Apache".
    Example:
    ServerBanner Apache/1.3.31 (Unix) PHP/4.3.9 mod_ssl/2.8.19 OpenSSL/0.9.7a
     

    DocumentRoot

    The document root. This can either be an absolute path or a path relative to the current working directory. If the document root does not exist the server will terminate during the first request. The default is "../htdocs/".
    Examples:
    DocumentRoot M:\
    DocumentRoot /www
     

    LogAccess

    Where to log accesses. You have three options:
    - log to a file you specify by either an absolute or a relative path
    - log to STDOUT (standard out) by using "STDOUT"
    - log to STDERR (standard error) by using "STDERR"
    The default is to log to STDOUT.
    Examples:
    LogAccess logs/access.log
    LogAccess /var/log/com.lukasfeiler.httpd/access.log LogAccess STDOUT LogAccess STDERR
     

    LogError

    Where to log errors. You have three options:
    - log to a file you specify by either an absolute or a relative path
    - log to STDOUT (standard out) by using "STDOUT"
    - log to STDERR (standard error) by using "STDERR"
    The default is to log to STDOUT.
    Examples:
    LogError logs/error.log
    LogError /var/log/com.lukasfeiler.httpd/error.log LogError STDOUT LogError STDERR
     

    LogSecure

    Where to log security events. These are: allowing and denying access via the plugins IPFilter, HTTPAuth and ExtensionFilter. You have three options:
    - log to a file you specify by either an absolute or a relative path
    - log to STDOUT (standard out) by using "STDOUT"
    - log to STDERR (standard error) by using "STDERR"
    The default is to log to STDOUT.
    Examples:
    LogSecure logs/secure.log
    LogSecure /var/log/com.lukasfeiler.httpd/secure.log LogSecure STDOUT LogSecure STDERR
     

    Plugins

    The plugins to load and apply to each request in the order specified. All official plugins are in the package com.lukasfeiler.httpd.plugins. The following plugins are officially available:
  • IPFilter - allows only requests from certain IP address ranges.
  • HTTPAuth - requires basic HTTP authentication for all requests.
  • ExtensionFilter - denies/allows requests based on the file's extension.
  • DirectoryIndex - uses specified files (e.g. index.html) as directory index.
  • AutoIndex - does a directory listing; without this plugin the user cannot do a directory listing.
  • ParseM3U - parses mp3 playlist files (*.m3u) so that an audio player can handle them correctly.
  • ReadFile - reads the requested file and outputs its contents


  • Please note that you can write your own plugins and load them by using the full class name (e.g. com.lukasfeiler.test.ParsePHP). Please see Writing your own plugins.
    The order in which the plugins are loaded is of a very high importance. Always specify the plugins that restrict access before the plugins that allow the user to do something useful (and therefore potentially dangerous). Put IPFilter before HTTPAuth to let only the allowed IP addresses see the HTTP authentication dialog. If a restrictive plugin (IPFilter, HTTPAuth, ExtensionFilter) denies access no more plugins will be run and an HTTP error code will be sent to the client. For the other plugins the following applies: if a plugin matches (AutoIndex because a directory was requested; DirectoryIndex because a directory was requested and an index file exists; ParseM3U because a *.m3u file was requested; ReadFile because a regular file was requested) no other plugins will be run. Therefore it is important to put ReadFile after all other plugins that read a file of a specific type (like ParseM3U). One last thing: the less plugins you load the better the performace will be. The default is just "ReadFile".
    Example:
    Plugins IPFilter HTTPAuth ExtensionFilter DirectoryIndex AutoIndex ParseM3U ReadFile
     

    Plugin Options

    The following options are all related to a specific plugin. If the corresponding plugin is not loaded the configuration option will not be parsed.
     

    IPFilterOrder

    In which order to apply IPFilterAllow and IPFilterDeny. Possible values are "Allow Deny" and "Deny Allow". If one of the two (IPFilterAllow and IPFilterDeny) matches, the other will not be evaluated. It is recommended to use "Allow Deny" (everything that is not specifically permitted is denied) instead of "Deny Allow" (everything that is not specifically denied is permitted). The default is "Allow Deny".
    Example:
    IPFilterOrder Allow Deny
     

    IPFilterAllow

    A list of IP addresses and IP address ranges to allow. A single IP address can be specified in the usual dotted quad notation (e.g. 192.168.1.10). IP address ranges must be specified as / (e.g. 192.168.1.0/255.255.255.0 to allow the whole 192.168.1.0 subnet). The keyword "ALL" can be used to match all IP addresses. The default is to allow no IP address.
    Examples:
    IPFilterAllow 192.168.1.10/255.255.255.0 127.0.0.1
    IPFilterAllow ALL
     

    IPFilterDeny

    A list of IP addresses and IP address ranges to deny. A single IP address can be specified in the usual dotted quad notation (e.g. 192.168.1.10). IP address ranges must be specified as / (e.g. 192.168.1.0/255.255.255.0 to allow the whole 192.168.1.0 subnet). The keyword "ALL" can be used to match all IP addresses. The default is "ALL" (to deny all IP addresses).
    Examples:
    IPFilterDeny 192.168.1.10/255.255.255.0 127.0.0.1
    IPFilterDeny ALL
     

    HTTPAuthPasswordFile

    The path to the password file to use for HTTP authentication. The path can either be absolute or relative to the current working directory. If this configuration option is not set or the specified file does not exist, no valid accounts will be available and all logins will be refused. Please note that the login file is read for every attempted login so that you do not have to restart the server after altering the login file.
    The format of the password file is as follows:
    - Every account (username and password) is placed on a seperate line.
    - An account is specified as "username:password"
    A password file could look like this:
    joedoe:9uZ9B2
    janedoe:ZUa23Y
    
    Examples:
    HTTPAuthPasswordFile ../conf/logins.pwd
    HTTPAuthPasswordFile /usr/local/com.lukasfeiler.httpd/conf/logins.pwd
     

    HTTPAuthRealm

    The realm to display in the browser's HTTP authentication dialog box. It will be sent in the "WWW-Authenticate" HTTP header. The default is "This is a restricted area." Examples:
    HTTPAuthRealm This is a restricted area.
    HTTPAuthRealm Unauthorized use of this system is prohibited and may be prosecuted to the fullest extent of the law.
     

    ExtensionFilterOrder

    In which order to apply ExtensionFilterAllow and ExtensionFilterDeny. Possible values are "Allow Deny" and "Deny Allow". If one of the two (ExtensionFilterAllow and ExtensionFilterDeny) matches, the other will not be evaluated. It is recommended to use "Allow Deny" (everything that is not specifically permitted is denied) instead of "Deny Allow" (everything that is not specifically denied is permitted). The default is "Allow Deny".
    Example:
    ExtensionFilterOrder Allow Deny
     

    ExtensionFilterAllow

    A list of file extensions to allow. The keyword "ALL" can be used to match all extensions. The default is to allow no extension.
    Examples:
    ExtensionFilterAllow .m3u .txt .mp3
    ExtensionFilterAllow ALL
     

    ExtensionFilterDeny

    A list of file extensions to deny. The keyword "ALL" can be used to match all extensions. The default is to deny all extension.
    Examples:
    ExtensionFilterDeny ALL
    ExtensionFilterDeny .exe .vbs .bat .doc .xls
     

    DirectoryIndex

    The files to try to find and display in the specified order if a directory was requested. The default is "index.html".
    Example: DirectoryIndex index.html index.txt

     

    ParseM3UHostPrefix

    The specified host will be prepended to all lines in a playlist file (*.m3u) that do not contain a slash.
    Actually "/" will be prepended. If you do not want to use any prefix do not include ParseM3U in the Plugin configuration option. The default is to prepend "http://:/". So if the server listens on the loopback interface (localhost) and you want the audio player handling the m3u files to contact http://www.lukasfeiler.com instead of localhost you would use the configuration option as in the first example. You can as well specify a port as in the second example.
    Examples:
    ParseM3UHostPrefix http://www.lukasfeiler.com
    ParseM3UHostPrefix http://law.lukasfeiler.com:8080

     

    A Sample Configuration File

    ListenPort 80
    ListenAddress localhost
    ConnectionTimeout 50000
    ConnectionLifetime 300000
    ConnectionMaximum 10
    ConnectionMaximumDelay 20000
    ConnectionQueueLength 1
    
    ServerBanner Apache
    
    DocumentRoot M:\
    
    Plugins IPFilter HTTPAuth ExtensionFilter AutoIndex ParseM3U ReadFile
    
    IPFilterOrder allow deny
    IPFilterAllow 192.168.1.10/255.255.255.0 127.0.0.1
    IPFilterDeny ALL
    
    
    ExtensionFilterOrder allow deny
    ExtensionFilterAllow .m3u .txt .mp3
    ExtensionFilterDeny ALL
    
    HTTPAuthPasswordFile conf/logins.pwd
    HTTPAuthRealm This is a restricted area.
    
    LogAccess logs/access.log
    LogError  logs/error.log
    LogSecure logs/secure.log
    

     

    Writing your own plugins

    Your plugins need to extend com.lukasfeiler.httpd.plugins.HTTPServerPlugin. You will need to implement a constructor and the method run. run has to return false if the plugin did not match and an different plugins should be run; if it returns true no other plugins will be run.
    Here is a sample implementation of a plugin that uses the CGI version of PHP to parse PHP files.
    The configuration option PHPCgi is used set the path to the PHP executable.
     
    package com.lukasfeiler.test;
    
    import com.lukasfeiler.httpd.*;
    import com.lukasfeiler.httpd.plugins.HTTPServerPlugin;
    import java.io.*;
    import java.util.Vector;
    
    /**Parses PHP files using the the php CGI executable.
    * You can use the configuration option PHPCgi to specify
    * the path to the php executable. The default is "php".
    */
    public class ParsePHP extends HTTPServerPlugin {
        /**parent constructor is protected; we need a public constructor*/
        public ParsePHP(HTTPServer server) {
            super(server);
        }
        
        /**Is called by HTTPSocket to run the plugin for a specific request*/
        public boolean run(HTTPSocket socket, File f, String requestedResource, Vector headers)  {
            //try another plugin if a directory was requested
            if (f.isDirectory()) {
                return false;
            }
            
            //try another plugin if the requested file does not have the extension .php
            if (!server.getFileExtension(f).equals(".php")) {
                return false;
            }
                            
            try {
                //read the value of the configuration option PHPCgi; use the "php" as a default
                String php = server.getConfigurationOption("PHPCgi", "php");
                
                //run the php CGI version with the filename as argument
                Process cmdProcess = Runtime.getRuntime().exec(php + " " + f.getCanonicalPath());
                
                //get the process output stream (an input stream form our perspective)
                InputStream cmdProcessIn = cmdProcess.getInputStream();
                
                //write standard HTTP headers and use text/html as content type
                socket.writeStandardHTTPHeaders("text/html");
                
                //read PHP output and write it to the socket stream
                int c = 0;
                while((c = cmdProcessIn.read()) != -1){
                    socket.getOut().write(
                        Character.toString((char)c).getBytes()
                    );
                }
                
                //everything went fine
                server.logAccess(socket, f, requestedResource, headers, 200, (int)f.length());
            } catch (IOException e) {
                //log an error and send a "500 Internal error" to the client
                socket.writeInternalError(socket, f, requestedResource, headers, e.getClass().getName() + ": " + e.getMessage());
            }
            
            //do not try to run another plugin for this request
            return true;
        }
    }