Accessing response headers using a decorator in Armeria

541 views Asked by At

I would like to add a decorator to my armeria client that checks each http response if a certain http header was returned:

    builder.decorator((delegate, ctx, req) -> {
      final HttpResponse response = delegate.execute(ctx, req);
      final AggregatedHttpResponse r = response.aggregate().join();
      for (Map.Entry<AsciiString, String> header : r.headers()) {
        if ("warning".equalsIgnoreCase(header.getKey().toString())) {
          throw new IllegalArgumentException("Detected usage of deprecated API for request "
            + req.toString() + ":\n" + header.getValue());
        }
      }
      return response;
    });

However, when starting my client it blocks on the join() call and waits forever. Is there a standard pattern for this in Armeria ? Presumably i cannot just block on the response in an interceptor, but i could not find a way to access the response headers otherwise. Using subscribe or toDuplicator did not work any better though.

1

There are 1 answers

0
trustin On BEST ANSWER

There are two ways to achieve the desired behavior.

The first option is to aggregate the response asynchronously and then convert it back to an HttpResponse. The key APIs are AggregatedHttpResponse.toHttpResponse() and HttpResponse.from(CompletionStage):

builder.decorator(delegate, ctx, req) -> {
    final HttpResponse res = delegate.serve(ctx, req);
    return HttpResponse.from(res.aggregate().thenApply(r -> {
        final ResponseHeaders headers = r.headers();
        if (headers...) {
            throw new IllegalArgumentException();
        }
        // Convert AggregatedHttpResponse back to HttpResponse.
        return r.toHttpResponse();
    }));
});

This approach is fairly simple and straightforward, but it doesn't work for a streaming response, because it waits until the complete response body is ready.

If your service returns a potentially large streaming response, you can use a FilteredHttpResponse to filter the response without aggregating anything:

builder.decorator(delegate, ctx, req) -> {
    final HttpResponse res = delegate.serve(ctx, req);
    return new FilteredHttpResponse(res) {
        @Override
        public HttpObject filter(HttpObject obj) {
            // Ignore other objects like HttpData.
            if (!(obj instanceof ResponseHeaders)) {
                return obj;
            }

            final ResponseHeaders headers = (ResponseHeaders) obj;
            if (headers...) {
                throw new IllegalArgumentException();
            }

            return obj;
        }
    };
});

It's slightly more verbose than the first option, but it does not buffer the response in the memory, which is great for large streaming responses.

Ideally, in the future, we'd like to add more operators to HttpResponse or StreamMessage. Please stay tuned to this issue page and add any suggestions for better API: https://github.com/line/armeria/issues/3097