Fork me on GitHub

Simple HttpServer APIs

The Simple HttpServer aims to provide web developers not just a server able to serve files over the HTTP protocol, but rather an API set to develop complete Web applications.

The Http model

The HTTP model APIs takes inspiration from the Servlet model, simplifying it keeping off all the container related features, just pure HTTP protocol:

UML class diagram

A detailed UML class diagram will help to understand the model design.

About classes organization

Main entities are the Request and Response: they both are arguments of the RequestHandler which is the entity responsible for the HTTP negotiation. A commodity BaseRequestHandler is provided in order to simplify the HTTP method handling, such as GET or POST, where users can implement the interested method handling.

Both Request and Response manipulate HTTP Headers as a MultiValued collection, a read-only key-value data structure where for each key can be associated more than one value. Request query string parameters and form-urlencoded parameters are implemented as MultiValued as well.

So, in order to implement their own web applications, users can start extending a BaseRequestHandler; the code snippet below shows how to implement a simple handler that provides static files found in a region in the File System, that can be requested via the HTTP GET method.

public final class FileRequestHandler
    extends BaseRequestHandler
{

    private final File baseDir;

    public FileRequestHandler( File baseDir )
    {
        this.baseDir = baseDir;
    }

    @Override
    protected void get( final Request request, final Response response )
        throws IOException
    {
        File requested = new File( baseDir, request.getPath() );
        if ( !requested.exists() )
        {
            response.setStatus( Response.Status.NOT_FOUND );
            return;
        }

        if ( requested.isDirectory() )
        {
            requested = new File( requested, "index.html" );
        }

        if ( requested.exists() )
        {
            response.setStatus( Response.Status.OK );
            response.setBody( new FileResponseBodyWriter( requested ) ); // a special writer that streams the file
        }
        else
        {
            response.setStatus( Response.Status.NOT_FOUND );
        }
    }

}

The Server interfaces

One of the Simple HttpServer ambitions is eliminating the boilerplate code to configure server instances, keeping off from the core implementation plain old textual configuration files, such as Properties or XML, and letting the server be configured with a pure Java mini EDSL (that of course can be proxed by textual representations, such as Properties, XML, JSON, YAML, ...).

UML class diagram

A detailed UML class diagram will help to understand the model design.

About classes organization

Users implement HttpServerConfiguration, the HttpServer passes a HttpServerConfigurator to user configuration, and user configuration uses the configurator to map path patterns to RequestHandlers.

public class MyServerConfiguration
    implements HttpServerConfiguration
{

    private final File siteDir = new File( System.getProperty( "user.dir" ), "site" );

    public void configure( HttpServerConfigurator configurator )
    {
        configurator.bindServerToHost( "localhost" );
        configurator.bindServerToPort( 8080 );
        configurator.serveRequestsWithThreads( 10 );
        configurator.sessionsHaveMagAge( 60 * 60 );
        configurator.keepAliveConnectionsHaveTimeout( 5 );

        configurator.serve( "/*" ).with( new FileRequestHandler( siteDir ) );
        configurator.when( Response.Status.NOT_FOUND ).serve( new File( siteDir, "404.html" ) );
        configurator.when( Response.Status.INTERNAL_SERVER_ERROR ).serve( new File( siteDir, "500.html" ) );
    }

}

The configuration allows users specify:

  • Server related data, such as the binding host and port, number of threads to serve requests, HTTP session max age and the Keep Alive timeout;
  • The handlers that have to be invoked when requesting paths - web.xml syntax supported!
  • The default response has to be provided when response provides a specific response status.

    DRY (Don't Repeat Yourself): Repeating configurator over and over for each configure step can get a little tedious. The Simple HttpServer package provides a support class named AbstractHttpServerConfiguration which implicitly gives you access to HttpServerConfigurator's methods.

    For example, extending AbstractHttpServerConfiguration and rewrite the above binding as:

    public class MyServerConfiguration
        extends AbstractHttpServerConfiguration
    {
    
        private final File siteDir = new File( System.getProperty( "user.dir" ), "site" );
    
        @Override
        protected void configure()
        {
            bindServerToHost( "localhost" );
            bindServerToPort( 8080 );
            serveRequestsWithThreads( 10 );
            sessionsHaveMagAge( 60 * 60 );
            keepAliveConnectionsHaveTimeout( 5 );
    
            serve( "/*" ).with( new FileRequestHandler( siteDir ) );
            when( Response.Status.NOT_FOUND ).serve( new File( siteDir, "404.html" ) );
            when( Response.Status.INTERNAL_SERVER_ERROR ).serve( new File( siteDir, "500.html" ) );
        }
    
    }

    I'll use this syntax throughout the rest of the guide.

    We can break HttpServer's lifecycle down into three distinct stages: init, runtime and stop:

  • during the init phase, the Server warms up his engine according to the passed configuration; a typical pattern would be:
    final HttpServerConfiguration configuration = new MyServerConfiguration();
    
    final HttpServer httpServer = ... //let's see how to obtain an HttpServer instance in the core module
    
    try
    {
        httpServer.init( configuration );
    }
    catch ( InitException ie )
    {
        logger.error( "Server cannot be initialized", ie );
    }
  • the server switches to the runtime phase when the start() method is invoked; please note that start() is a blocking method, so all the checks/interruption have to be performed by a monitoring separated thread:
    try
    {
        httpServer.start();
    }
    catch ( RunException re )
    {
        logger.error( "Server cannot be started", re );
    }
  • server status can be monitored by invoking the getStatus() method: maybe the server accidentally crashed, so a monitor could re-init it or send alarms:
    final HttpServer httpServer = ... 
    httpServer.init( configuration );
    httpServer.start();
    
    new Thread()
    {
    
        public void run()
        {
            if ( HttpServer.Status.STOPPED == httpServer.getStatus() )
            {
                sendAlertMail();
            }
            else
            {
                noOp();
            }
        }
    
    }.start();
  • server can be stopped inside the JVM ShutdownHook thread, i.e.:
    Runtime.getRuntime().addShutdownHook( new Thread()
    {
    
        public void run()
        {
            try
            {
                httpServer.stop();
            }
            catch ( ShutdownException e )
            {
                logger.error( "Execution terminated with errors", se );
            }
        }
    
    }

Maven Users

Apache Maven users can include APIs artifact by including the following dependency:

  <dependencies>
    ...
    <dependency>
      <groupId>org.99soft.shs</groupId>
      <artifactId>shs-api</artifactId>
      <version>0.1</version>
      <scope>provided</scope>
    </dependency>
    ...
  </dependencies>

APIs can be included in provided scope since the SHS runtime already brings them.