> Docs > Introduction

Introduction

bayou.io is a set of APIs and utilities for Java web development.

Currently we have HTTP server, HTTP client, HTML builder, WebSocket server, and Async API.

The following is a tutorial of features. See Docs for detailed subjects.

HttpServer - Hello World

HttpServer is an embedded, asynchronous HTTP server.

HttpServer requires an HttpHandler which accepts HttpRequest and generates HttpResponse.

public class MyHandler implements HttpHandler
{
    @Override
    public Async<HttpResponse> handle(HttpRequest request)
    {
        return HttpResponse.text(200, "Hello World");
    }

Create and start an HttpServer with the handler:

    public static void main(String... args) throws Exception
    {
        HttpHandler handler = new MyHandler();

        HttpServer server = new HttpServer(handler);
        server.conf().trafficDump(System.out::print);
        server.start();
    }

and we have a server running at http://localhost:8080.

Hot Reload

Before trying more examples, let's enable hot-reload to reduce turnaround time.

Wrap the handler with HotHttpHandler, and start the server again:

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

The "hot" handler will monitor source file changes, and reload MyHandler when needed.

Now we don't need to restart the server after code change. Just save the source file and refresh the browser window.

Async

Note that HttpHandler returns Async<HttpResponse>. Async<T> represents an async action which may not have completed yet. See Async Programming.

As an example, let's add 1 second delay to our responses

    public Async<HttpResponse> handle(HttpRequest request)
    {
        Async<Void> delay = Fiber.sleep( Duration.ofSeconds(1) );
        return delay.then(v ->
            HttpResponse.text(200, "Hello World\n"+ Instant.now()));
    }

Serving a File

To serve an arbitrary file, use HttpResponse.file()

    public Async<HttpResponse> handle(HttpRequest request)
    {
        switch(request.uriPath()) // simplistic routing
        {
            case "/":
                return HttpResponse.html(200, "<video src='/test.mp4' controls>");

            case "/test.mp4":
                return HttpResponse.file(200, "/tmp/189913.mp4");

            default:
                return HttpResponse.file(404, "./404.html");
        }
    }

Manipulating Responses

HttpResponse objects can be inspected, transformed, cached, and shared.

public class MyHandler implements HttpHandler
{
    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)
    {
        switch(request.uriPath())
        {
            case "/test.txt":
                return stockResponse;

Serving Static Files

A StaticHandler maps a URI prefix to a directory prefix. It can be used to create a static-only file server.

    HttpHandler handler = new StaticHandler("/", ROOT_DIR);
    HttpServer server = new HttpServer(handler);
    server.conf().port(8081);
    server.start();

More often though, a StaticHandler is used by a parent handler that serves both static and dynamic contents:

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.
        switch(request.uriPath())
            ...
    }

The static files can be configured individually for headers, caching, etc.

Tagged URI

StaticHandler supports tagged URI - URI with resource ETag embedded in it. The response to a tagged URI never expires and can be cached forever. This is good for resources like css, js, favicon, etc. (See the <head> section of this page.)

In the following example, /test.html references an image through its tagged URI:

            case "/test.html":

                String imgUri = staticHandler.uri("img/foo.gif");
                //e.g. imgUri = "/img/foo.gif?t-53486116-314fb010"

                String html = String.format("<img src='%s'> %s", imgUri, imgUri);
                return HttpResponse.html(200, html);

Revisiting http://localhost:8080/test.html will not trigger a new request to the image, unless the image file is modified.

Html Builder

Html Builder is a set of Java APIs for building html trees. For example, to build an Html5Doc

public class HelloHtml extends Html5Doc
{
    public HelloHtml(String name)
    {
        _head(
            _link().rel("stylesheet").type("text/css").href("/main.css"),
            _title("Hello")
        );

        _body(() ->
        {
            if(name==null)
                _p("Hello, whoever you are.");
            else
                _form().action("/q").add(
                    "Hello ", _input().readonly(true).value(name)
                );
        });
    }
}

// http handler ...

        case "/hello.html":
            String name = request.uriParam("name");
            HtmlDoc html = new HelloHtml(name);
            return html.toResponse(200);

This will generate an html response, something like

<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" type="text/css" href="/main.css">
        <title>Hello</title>
    </head>
    <body>
        <form action="/q">
            Hello <input readonly value="world">
        </form>
    </body>
</html>

An html tree can be built and modified in flexible ways. For example, we can add an element to <head> much later in the code. It's also easy to design template hierarchy, reusable routines, and custom components.

CookieJar

User cookies can be managed as a mutable, map-like data structure by CookieJar

CookieJar cookieJar = CookieJar.current();  // fiber-local

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

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

Form Handling

In this demonstration, we use Html Builder to construct a web form, which, when submitted, is parsed into a FormData

public class FormHtml extends Html5Doc
{
    public FormHtml()
    {
        _body(
            _form().method("POST").action("/post").enctype(FormData.ENC_MULTIPART).add(
                CsrfToken._input(),  // hidden input for csrf token
                _input().name("s1"), _br(),
                _input().name("f1").type("file"), _br(),
                _button("upload")
            )
        );
    }
}

// http handler ...

    if(!request.method().equals("POST"))        // GET or HEAD
        return new FormHtml().toResponse(200);  // display the form

    return FormData.parse(request)           // Async<FormData>
        .timeout( Duration.ofSeconds(20) )   // max upload time
        .then( fd ->                         // FormData -> HttpResponse
        {
            String result = fd.param("s1") + "\n" + fd.file("f1");
            //fd.deleteFiles();
            return HttpResponse.text(200, result);
        })
        .catch_(CsrfException.class, e ->
                HttpResponse.text(403, "Cookie disabled?")
        )
        .catch_(Exception.class, e ->
                HttpResponse.text(400, "Try again.")
        );

HttpClient

Use HttpClient to send requests and receive responses.

HttpClient client = new HttpClient();
Async<HttpResponse> asyncRes = client.doGet( "https://example.com" );

HttpClient can be used in HttpHandler. For example, to create a simple HTTP proxy:

HttpServer proxy = new HttpServer( request ->
    client.send0(request, null)
);

WebSocketServer

A WebSocketHandler handles WebSocket handshakes

public class MyWsHandler implements WebSocketHandler
{
    @Override
    public Async<WebSocketResponse> handle(WebSocketRequest request)
    {
        // note: same-origin has already been enforced (default configuration)

        if( isBanned(request.ip()) )
            return WebSocketResponse.reject(403, "permission denied");
        else
            return WebSocketResponse.accept( this::handleChannel );
    }

    Async<Void> handleChannel(WebSocketChannel channel)
    {
        // work with the channel after a successful handshake
    }
}

Create a WebSocketServer with the handler, and attach it to an existing http server:

public static void main(String[] args) throws Exception
{
    HttpServer httpServer = new HttpServer( req->HttpResponse.file(200, "/tmp/ws.test.html") );
    httpServer.conf().trafficDump( System.out::print );

    WebSocketServer wsServer = new WebSocketServer( httpServer, new MyWsHandler() );
    wsServer.conf().trafficDump( System.err::print );

    httpServer.start();
}

A WebSocketChannel contains methods to read/write WebSocketMessage. Example of a simple echo server:

    Async<Void> handleChannel(WebSocketChannel channel)
    {
        return AsyncIterator
            .forEach_( channel::readMessage, channel::writeMessage )
            .finally_( channel::close );
    }

Conclusion

bayou.io presents exciting new ways of modeling and building web applications.