> Docs > 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
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.
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.
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())); }
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"); } }
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;
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.
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 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.
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");
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.") );
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) );
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 ); }
bayou.io
presents exciting new ways of modeling and building web applications.