> 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.