`AggregatedHttpResponseHandler` doesn't respect `ServiceRequestContext.additionalResponse{Headers,Trailers}`
See original GitHub issueUnlike HttpResponseSubscriber, AggregatedHttpResponseHandler doesn’t merge ServiceRequestContext.additionalResponseHeaders and additionalResponseTrailers when encoding response headers and trailers:
The following patch reproduces the problem by modifying AbstractUnaryGrpcServiceTest:
diff --git grpc/src/test/java/com/linecorp/armeria/server/grpc/protocol/AbstractUnaryGrpcServiceTest.java grpc/src/test/java/com/linecorp/armeria/server/grpc/protocol/AbstractUnaryGrpcServiceTest.java
index 0e07caee8..9da85a810 100644
--- grpc/src/test/java/com/linecorp/armeria/server/grpc/protocol/AbstractUnaryGrpcServiceTest.java
+++ grpc/src/test/java/com/linecorp/armeria/server/grpc/protocol/AbstractUnaryGrpcServiceTest.java
@@ -21,6 +21,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.io.UncheckedIOException;
import java.util.concurrent.CompletionStage;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
@@ -61,8 +62,10 @@ import com.linecorp.armeria.testing.server.ServiceRequestContextCaptor;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
+import io.grpc.Metadata;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
+import io.grpc.stub.MetadataUtils;
class AbstractUnaryGrpcServiceTest {
private static final String METHOD_NAME = "/armeria.grpc.testing.TestService/UnaryCall";
@@ -94,6 +97,7 @@ class AbstractUnaryGrpcServiceTest {
} catch (InvalidProtocolBufferException e) {
throw new UncheckedIOException(e);
}
+ ctx.setAdditionalResponseTrailer("mytest", "value");
if (request.hasResponseStatus()) {
// For statusExceptionUpstream() and statusExceptionDownstream()
assertThat(request).isEqualTo(EXCEPTION_REQUEST_MESSAGE);
@@ -125,15 +129,23 @@ class AbstractUnaryGrpcServiceTest {
@ParameterizedTest
@ArgumentsSource(UnaryGrpcSerializationFormatArgumentsProvider.class)
void normalDownstream(SerializationFormat serializationFormat) throws Exception {
+ final AtomicReference<Metadata> headersCapture = new AtomicReference<>();
+ final AtomicReference<Metadata> trailersCapture = new AtomicReference<>();
final TestServiceBlockingStub stub =
GrpcClients.newClient(server.httpUri(serializationFormat),
- TestServiceBlockingStub.class);
+ TestServiceBlockingStub.class).withInterceptors(
+ MetadataUtils.newCaptureMetadataInterceptor(headersCapture, trailersCapture));
final SimpleResponse response = stub.unaryCall(REQUEST_MESSAGE);
assertThat(response).isEqualTo(RESPONSE_MESSAGE);
final ServiceRequestContextCaptor captor = server.requestContextCaptor();
final HttpHeaders trailers = GrpcWebTrailers.get(captor.take());
assertThat(trailers).isNotNull();
assertThat(trailers.getInt(GrpcHeaderNames.GRPC_STATUS)).isZero();
+ if (serializationFormat.toString().equals("gproto")) {
+ assertThat(trailersCapture.get().get(Metadata.Key.of("mytest",
+ Metadata.ASCII_STRING_MARSHALLER))).isEqualTo(
+ "value");
+ }
}
@Test
@@ -153,9 +165,12 @@ class AbstractUnaryGrpcServiceTest {
@ParameterizedTest
@ArgumentsSource(UnaryGrpcSerializationFormatArgumentsProvider.class)
void statusExceptionDownstream(SerializationFormat serializationFormat) throws Exception {
+ final AtomicReference<Metadata> headersCapture = new AtomicReference<>();
+ final AtomicReference<Metadata> trailersCapture = new AtomicReference<>();
final TestServiceBlockingStub stub =
GrpcClients.newClient(server.httpUri(serializationFormat),
- TestServiceBlockingStub.class);
+ TestServiceBlockingStub.class).withInterceptors(
+ MetadataUtils.newCaptureMetadataInterceptor(headersCapture, trailersCapture));
assertThatThrownBy(() -> stub.unaryCall(EXCEPTION_REQUEST_MESSAGE))
.isInstanceOfSatisfying(StatusRuntimeException.class, cause -> {
final Status status = cause.getStatus();
@@ -167,6 +182,10 @@ class AbstractUnaryGrpcServiceTest {
final HttpHeaders trailers = GrpcWebTrailers.get(captor.take());
assertThat(trailers).isNotNull();
assertThat(trailers.getInt(GrpcHeaderNames.GRPC_STATUS)).isEqualTo(StatusCodes.PERMISSION_DENIED);
+ if ("gproto".equals(serializationFormat.toString())) {
+ assertThat(trailersCapture.get().get(
+ Metadata.Key.of("mytest", Metadata.ASCII_STRING_MARSHALLER))).isEqualTo("value");
+ }
}
@Test
Issue Analytics
- State:
- Created a year ago
- Reactions:1
- Comments:5 (2 by maintainers)
Top Results From Across the Web
No results found
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found

At second thought, I came to the conclusion that this is not what we really want because:
Let me close this issue and open a new one with a better idea, given the original issue description is not correct anyway.
I see. According to gRPC specification:
What if we put
grpc-statusandgrpc-messagein HTTP/2 trailers, then?