> Docs > Http Server

Http Server

An HttpServer receives requests from clients, sends back responses, through an HttpHandler.

HttpHandler

Interface HttpHandler has a single abstract method for creating responses for requests.

    Async<HttpResponse> handle(HttpRequest request);

An HttpServer requires an HttpHandler, for example

HttpHandler handler = request -> HttpResponse.text(200, "Hello");

new HttpServer(handler).start(); // localhost:8080

Usually, http handlers are more complex that that, and it's better to define a class for you handler.

public class MyHandler implements HttpHandler
{
    @Override
    public Async<HttpResponse> handle(HttpRequest request)
    {
        switch(request.uriPath())
        {
            case "/hello" : return HttpResponse.text(200, "Hello");
            ...
            default: return HttpResponse.text(404, "Not Found");
        }
    }

    public static void main(String... args) throws Exception
    {
        HttpHandler handler = new MyHandler();
        HttpServer server = new HttpServer(handler);
        server.conf()
            .port(8080)
            .trafficDump(System.out::print) // for debugging
            ;
        server.start();
    }
}

The http handler does not need to worry about HEAD, Range, Conditional requests; treat them as normal GET requests, and the server will transform the response appropriately.

Hot Reload

Use HotHttpHandler to hot-reload the server app when source code changes. This is a convenience during development, but not recommended on a production server.

        // HttpHandler handler = new MyHandler();
        HotHttpHandler handler = new HotHttpHandler(MyHandler.class).onJavaFiles(SRC_DIR);

        HttpServer server = new HttpServer(handler);
        ...

The hot handler will create an instance of MyHandler in a separate classloader, and forward handle(request) calls to it. Whenever HotHttpHandler.handle(request) is called, if some .java files under SRC_DIR have been created/updated/deleted since the last handle(request) call, the source files will be recompiled, and a new instance of MyHandler will be created in a new classloader.

When reloading occurs, new requests will be handled by the new handler instance; previous outstanding requests are still being served by the old handler instance.

Reloading only applies to MyHandler and code it can reach. If other parts of the code are changed, e.g. server configuration, you may need to restart the JVM.

Sometimes, java recompilation does not work perfectly (e.g. when a constant field is changed). When in doubt, restart JVM.

If you prefer to reload on .class file changes instead (because your IDE does auto-compile on save), do

        HotHttpHandler handler = new HotHttpHandler(MyHandler.class).onClassFiles();

You may also want to reload on changes of other types of files, for example

        HotHttpHandler handler = new HotHttpHandler(MyHandler.class)
                                     .onJavaFiles(SRC_DIR)
                                     .onFiles("prop file", "glob:**.properties", SRC_DIR);

For detailed mechanism of hot reloading, see HotReloader.

Reading Request

The HttpRequest interface contains request information like method, uri, headers.

If a request contains a body (for example, POST form data), it is represented in the request entity. See Entity and Body. See also Form Handling and Json.

When the server invokes handle(request) on the http handler, the request becomes the current fiber-local request, which can be retrieved by HttpRequest.current().

Creating Response

An http handler returns Async<HttpResponse>, i.e. it produces responses asynchronously. See Async Programming. For example, to add a delay to a response:

            case "/delay" :
                return Fiber.sleep(Duration.ofSeconds(1))
                    .then( v -> HttpResponse.text(200, "yes?") );

The HttpResponse interface is readonly, describing the response status, headers, cookies, entity.

To create an instance of HttpResponse, use mutable HttpResponseImpl. For example

HttpEntity entity = new TextHttpEntity("Bye");
HttpResponseImpl response = new HttpResponseImpl( HttpStatus.c200_OK, entity )
                            .header(Headers.Connection, "close")
                            .cookie("c1", "v1", Cookie.SESSION);
return response;

HttpResponseImpl implements Async<HttpResponse> therefore it can be returned directly in HttpHandler.handle(request).

There are static factory methods in HttpResponse for common responses, for example, text(status, texts), file(status, filePath), redirect(uri). Most of these methods return mutable HttpResponseImpl which the caller can make further modifications, for example

                return HttpResponse.text(200, "Bye")
                        .header(Headers.Connection, "close")
                        .cookie("c1", "v1", Cookie.SESSION);

To create HTML responses, consider Html Builder.

To create custom responses, see Entity and Body.

One instance of HttpResponse may be used to serve multiple requests, for example,

    final Async<HttpResponse> stockResponse
            = HttpResponse.file(200, "/tmp/big.txt")
                          .then(HttpResponse::gzip)    // compress
                          .then(HttpResponse::cache);  // cache in memory

    public Async<HttpResponse> handle(HttpRequest request)
    {
        ...
                return stockResponse;

Json

Currently Bayou does not directly support Json. However, it shouldn't be too hard to plug in your favorite Json library. Suppose you have the following methods for conversion between Json strings and Java objects:

static Object json2obj(String json){ ... }
static String obj2json(Object obj ){ ... }

you can then parse Json requests and generate Json responses like

static Async<Object> parseJsonRequest(HttpRequest request)
{
    HttpEntity entity = request.entity();
    if(entity==null || !entity.contentType().types().equals("application/json"))
        return Async.failure(new Exception("no json entity"));

    Async<String> json = entity.body().asString(1000);
    return json.map( str->json2obj(str) );
}

static HttpResponseImpl createJsonResponse(Object obj)
{
    String json = obj2json(obj);
    ContentType contentType = ContentType.parse("application/json; charset=UTF-8");
    HttpEntity entity = new TextHttpEntity( contentType, json );
    return new HttpResponseImpl( HttpStatus.c200_OK, entity );
}

Cookies

Request cookies can be read from HttpRequest.cookies(), and response cookies can be specified in HttpResponse.cookies().

String v1 = request.cookie("c1");

HttpResponseImpl response = HttpResponse.text(200, "foo");
response.cookie("c1", "",   Cookie.DELETE );
response.cookie("c2", "v2", Cookie.SESSION);

However, it may be more convenient to use CookieJar which models cookies as a mutable map-like data structure.

CookieJar cookieJar = CookieJar.current(); // for the current request-response cycle

String foo = cookieJar.get("foo");
if(foo==null)
    cookieJar.put("foo", foo="bar", Cookie.SESSION);

String flashMsg = CookieJar.current().remove("flashMsg");

This gives the illusion that we are directly reading and modifying the client's cookie storage.

CookieJar is a fiber-local concept, it can be used without reference to the current request/response.

The properties of cookies, particularly the values, are constrained, see Cookie class. That means you cannot put arbitrary strings in a CookieJar. We leave it to programmers to build higher abstractions on top of CookieJar, for example, to encode/sign/encrypt cookie values.

StaticHandler

To serve multiple static files under a directory, use StaticHandler, which maps a URI prefix to a directory prefix.

new StaticHandler("/", ROOT_DIR);

new StaticHandler("/data", DATA_DIR);

A StaticHandler can be used directly for an HttpServer

HttpHandler handler = new StaticHandler("/", ROOT_DIR);
HttpServer server = new HttpServer(handler);

However, usually a StaticHandler is used in a parent handler:

public class MyHandler implements HttpHandler
{
    final StaticHandler staticHandler = new StaticHandler("/", ROOT_DIR);

    @Override
    public Async<HttpResponse> handle(HttpRequest request)
    {
        // try static files first
        HttpResponseImpl resp = staticHandler.handle(request);
        if(resp.statusCode()!=404) // 200, or error
             return resp;

        // 404 from staticHandler, request is not mapped to a static file.
        ...
    };

Each file in a static handler can be configured individually through StaticFileConf. For example

    ... new StaticHandler("/", ROOT_DIR, this::confMod)

    void confMod(StaticFileConf conf)
    {
        if(conf.fileSize<5000)
            conf.cache(true); // cache the file in memory if it's small

        conf.header("Cache-Control", "public"); // for every file
    }

StaticHandler supports "tagged" URI, which contains the ETag of the file. The response for a tagged URI never expires, therefore the browser can cache the response indefinitely. The tagged URI for a file can be obtained by StaticHandler.uri( relativeFilePath )

// in an html template
_link().rel("stylesheet").type("text/css").href( staticHandler.uri("css/main.css") )

Server Configuration

Configuration of an HttpServer is done on HttpServerConf, which can be retrieved by HttpServer.conf(). Configuration must be done before server starts.

server.conf().port( 8080 );
server.conf().trafficDump( System.out::print );
server.start();

The config methods can be chained if you prefer.

server.conf()
    .port( 8080 )
    .maxConnectionsPerIp( 5 )
    .autoGzip( true )
    //.trafficDump( System.out::print )
    ;

See also