> Docs > Http Server
An HttpServer
receives requests from clients, sends back responses, through an 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.
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
.
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()
.
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;
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 ); }
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.
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") )
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