dev.mccue/microhttp-handler
dev.mccue.microhttp.handler
bowbahdoe/microhttp-handler
microhttp-handler
provides interfaces for making composable handlers for microhttp
.
There are two interfaces in the module, IntoResponse
and Handler
.
IntoResponse
is something which can be converted into a Response
. Handler
is a function which takes a Request
and returns something which implements IntoResponse
.
There are also two implementations of Handler
provided for convenience - RouteHandler
and DelegatingHandler
.
RouteHandler
checks the request's method and uri and returns null
if it doesn't match a chosen method and regex.
DelegatingHandler
tries a list of handlers in order, returning the first non-null response or falling back to a default for when no match is found. Combined with RouteHandler
, this can act as a very basic request router.
A normal microhttp handler takes a Request
and a Consumer<Response>
that should be called later.
While this is fine, in the age of virtual threads there isn't much downside to modeling handlers as functions that takes Request
s and return Response
s and there are many upsides to doing so.
The programming model is simpler to compose, simpler to test, and it provides an opportunity to introduce concepts such as middleware and IntoResponse
IntoResponse
is useful because making the normal Response
record requires dealing with reason phrases, content-type headers, and body encoding. IntoResponse
provides a seam for custom types to box up much of that logic.
import org.microhttp.EventLoop;
import org.microhttp.Response;
import org.microhttp.Header;
import dev.mccue.microhttp.handler.Handler;
import dev.mccue.microhttp.handler.RouteHandler;
import dev.mccue.microhttp.handler.DelegatingHandler;
import dev.mccue.reasonphrase.ReasonPhrase;
import java.util.List;
import java.util.regex.Pattern;
TextResponse(int status, String value)
record implements IntoResponse {
@Override
public Response intoResponse() {
return new Response(
,
status.forStatus(status),
ReasonPhraseList.of(new Header("Content-Type", "text/plain")),
.getBytes()
value);
}
}
void main() throws Exception {
Handler index = RouteHandler.of(
"GET",
Pattern.compile("/"),
-> new TextResponse(200, "Hello, world")
request );
Handler rootHandler = new DelegatingHandler(
List.of(index),
new TextResponse(404, "Not Found")
);
= new TextResponse(500, "Internal Error");
var error
= new EventLoop((request, callback) -> {
var eventLoop Thread.startVirtualThread(() -> {
try {
.accept(
callback.handle(request)
rootHandler.intoResponse()
);
} catch (Exception e) {
.accept(error.intoResponse());
callback}
});
});
.start();
eventLoop.join();
eventLoop}