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 APIs takes inspiration from the Servlet model, simplifying it keeping off all the container related features, just pure HTTP protocol:
A detailed UML class diagram will help to understand the model design.
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 ); } } }
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, ...).
A detailed UML class diagram will help to understand the model design.
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:
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:
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 ); }
try { httpServer.start(); } catch ( RunException re ) { logger.error( "Server cannot be started", re ); }
final HttpServer httpServer = ... httpServer.init( configuration ); httpServer.start(); new Thread() { public void run() { if ( HttpServer.Status.STOPPED == httpServer.getStatus() ) { sendAlertMail(); } else { noOp(); } } }.start();
Runtime.getRuntime().addShutdownHook( new Thread() { public void run() { try { httpServer.stop(); } catch ( ShutdownException e ) { logger.error( "Execution terminated with errors", se ); } } }
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.