Request content preview isn't produced if decorate a service with `ThrottlingService`
See original GitHub issueI found that RequestLog#requestContentPreview returns null when I decorate a service using ContentPreviewingService and ThrottlingService.
The request content preview is not produced when aggregate a service which is decorated with following structure:
.decorator(((delegate, ctx, req) -> HttpResponse.from(
completedFuture(null).handleAsync((ignored, cause) -> {
try {
return delegate.serve(ctx, req);
} catch (Exception e) {
return Exceptions.throwUnsafely(e);
}
}, ctx.eventLoop()))))
Following test can reproduce:
class RequestContentPreviewTest {
private static final AtomicReference<ServiceRequestContext> ctxCaptor = new AtomicReference<>();
@RegisterExtension
static ServerExtension server = new ServerExtension() {
@Override
protected void configure(ServerBuilder builder) {
builder.service("/", (ctx, req) -> {
ctxCaptor.set(ctx);
return HttpResponse.from(req.aggregate().thenApply(
aggregated -> HttpResponse.of("Hello, " + aggregated.contentUtf8() + "!")));
})
.decorator(LoggingService.builder()
.requestLogLevel(LogLevel.INFO)
.successfulResponseLogLevel(LogLevel.INFO)
.failureResponseLogLevel(LogLevel.WARN)
.newDecorator())
.decorator(ContentPreviewingService.newDecorator(Integer.MAX_VALUE))
.decorator(((delegate, ctx, req) -> HttpResponse.from(
completedFuture(null).handleAsync((ignored, cause) -> {
try {
return delegate.serve(ctx, req);
} catch (Exception e) {
return Exceptions.throwUnsafely(e);
}
}, ctx.eventLoop()))));
}
};
@Test
void requestContentPreviewH1C() {
final WebClient client = WebClient.of(SessionProtocol.H1C,
Endpoint.of("127.0.0.1", server.httpPort()));
final RequestHeaders headers = RequestHeaders.of(HttpMethod.POST, "/",
HttpHeaderNames.CONTENT_TYPE, "text/plain");
final AggregatedHttpResponse res = client.execute(headers, HttpData.ofUtf8("Armeria"))
.aggregate()
.join();
assertThat(res.contentUtf8()).isEqualTo("Hello, Armeria!");
final ServiceRequestContext ctx = ctxCaptor.get();
final RequestLog log = ctx.log().whenComplete().join();
assertThat(log.responseContentPreview()).isEqualTo("Hello, Armeria!");
// expected: "Armeria"
// but was: null
// org.opentest4j.AssertionFailedError:
// expected: "Hello, World!"
assertThat(log.requestContentPreview()).isEqualTo("Armeria");
}
@Test
void requestContentPreviewHTTP() {
final WebClient client = WebClient.of(SessionProtocol.HTTP,
Endpoint.of("127.0.0.1", server.httpPort()));
final RequestHeaders headers = RequestHeaders.of(HttpMethod.POST, "/",
HttpHeaderNames.CONTENT_TYPE, "text/plain");
final AggregatedHttpResponse res = client.execute(headers, HttpData.ofUtf8("Armeria"))
.aggregate()
.join();
assertThat(res.contentUtf8()).isEqualTo("Hello, Armeria!");
final ServiceRequestContext ctx = ctxCaptor.get();
final RequestLog log = ctx.log().whenComplete().join();
assertThat(log.responseContentPreview()).isEqualTo("Hello, Armeria!");
assertThat(log.requestContentPreview()).isEqualTo("Armeria");
}
}
logging H1C:
02:08:33.724 [armeria-common-worker-nio-2-2] INFO c.l.a.server.logging.LoggingService - [sreqId=64b5968f, chanId=ec5eedd2, raddr=127.0.0.1:54446, laddr=127.0.0.1:54445][h1c://al01540922.local/#POST] Request: {startTime=2021-09-08T17:08:33.696Z(1631120913696000), length=7B, duration=24472µs(24472956ns), scheme=none+h1c, name=POST, headers=[:method=POST, :path=/, :scheme=http, host=127.0.0.1:54445, content-type=text/plain, user-agent=armeria/1.11.1-SNAPSHOT, content-length=7]}
02:08:33.741 [armeria-common-worker-nio-2-2] INFO c.l.a.server.logging.LoggingService - [sreqId=64b5968f, chanId=ec5eedd2, raddr=127.0.0.1:54446, laddr=127.0.0.1:54445][h1c://al01540922.local/#POST] Response: {startTime=2021-09-08T17:08:33.737Z(1631120913737000), length=15B, duration=3305µs(3305059ns), totalDuration=43624µs(43624769ns), headers=[:status=200, content-type=text/plain; charset=utf-8, content-length=15], contentPreview=Hello, Armeria!}
logging H2C:
02:08:33.884 [armeria-common-worker-nio-2-4] INFO c.l.a.server.logging.LoggingService - [sreqId=d4e30197, chanId=1d1d7144, raddr=127.0.0.1:54447, laddr=127.0.0.1:54445][h2c://al01540922.local/#POST] Request: {startTime=2021-09-08T17:08:33.882Z(1631120913882000), length=7B, duration=1976µs(1976048ns), scheme=none+h2c, name=POST, headers=[:method=POST, :path=/, :scheme=http, :authority=127.0.0.1:54445, content-type=text/plain, user-agent=armeria/1.11.1-SNAPSHOT, content-length=7], contentPreview=Armeria}
02:08:33.886 [armeria-common-worker-nio-2-4] INFO c.l.a.server.logging.LoggingService - [sreqId=d4e30197, chanId=1d1d7144, raddr=127.0.0.1:54447, laddr=127.0.0.1:54445][h2c://al01540922.local/#POST] Response: {startTime=2021-09-08T17:08:33.884Z(1631120913884000), length=15B, duration=1648µs(1648011ns), totalDuration=3309µs(3309007ns), headers=[:status=200, content-type=text/plain; charset=utf-8, content-length=15], contentPreview=Hello, Armeria!}
It works well if not decorated by the last one.
What I found is that https://github.com/line/armeria/blob/dbc7d4d3a324aac525acbdce134fe56c34186a4f/core/src/main/java/com/linecorp/armeria/internal/logging/ContentPreviewingUtil.java#L62 is called before https://github.com/line/armeria/blob/dbc7d4d3a324aac525acbdce134fe56c34186a4f/core/src/main/java/com/linecorp/armeria/internal/logging/ContentPreviewingUtil.java#L82
And this is because a CloseEvent from Http1RequestDecoder is already in queue before collect.
Issue Analytics
- State:
- Created 2 years ago
- Reactions:2
- Comments:7 (7 by maintainers)

Top Related StackOverflow Question
I think I was confused. Please ignore 😅
When I locally tested it,
handleCloseEventseems to be called byreq.aggregate()in service. It should be a bug ifhandleCloseEventis called before subscription.