Search code examples
armeria

Right way to add a per-request response delay to a custom HttpService


Here's my current implementation of HttpService.serve()

@Override
public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) throws Exception {
    return HttpResponse.from(req.aggregate().thenApply(ahr -> {
        MyResponse myResponse = Utils.handle(ahr);
        HttpResponse httpResponse Utils.toResponse(myResponse);
        return httpResponse; 
    }));
}

I have a user-defined response delay which can vary per each individual request-response, and this is available in the myResponse object.

What is the best way to apply this delay in a non-blocking way, I can see some delay API-s but they are protected within HttpResponse . Any extra tips or pointers to the streaming API design or decorators would be helpful. I'm really learning a lot from the Armeria code base :)


Solution

  • If you know the desired delay even before consuming the request body, you can simply use HttpResponse.delayed():

    @Override
    public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) throws Exception {
        return HttpResponse.delayed(
            HttpResponse.of(200),
            Duration.ofSeconds(3),
            ctx.eventLoop());
    }
    

    If you need to consume the content or perform some operation to calculate the desired delay, you can combine HttpResponse.delayed() with HttpResponse.from():

    @Override
    public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) throws Exception {
        return HttpResponse.from(req.aggregate().thenApply(ahr -> {
        //                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
            MyResponse myResponse = Utils.handle(ahr);
            HttpResponse httpResponse = Utils.toResponse(myResponse);
            Duration myDelay = Utils.delayMillis(...);
            return HttpResponse.delayed(httpResponse, myDelay, ctx.eventLoop());
            //                  ^^^^^^^
        });
    }
    

    If the delay is not actually delay but waiting for something to happen, you can use CompletableFuture.thenCompose() which is more powerful:

    @Override
    public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) throws Exception {
        return HttpResponse.from(req.aggregate().thenCompose(ahr -> {
        //                                       ^^^^^^^^^^^
            My1stResponse my1stRes = Utils.handle(ahr);
    
            // Schedule some asynchronous task that returns another future.
            CompletableFuture<My2ndResponse> myFuture = doSomething(my1stRes);
    
            // Map the future into an HttpResponse.
            return myFuture.thenApply(my2ndRes -> {
                HttpResponse httpRes = Utils.toResponse(my1stRes, my2ndRes);
                return httpRes;
            });
        });
    }
    

    For even more complicated workflow, I'd recommend you to look into Reactive Streams implementations such as Project Reactor and RxJava, which provides the tools for avoiding the callback hell. 😄