From ced5936c02218f0663a36d24aba63687efe55563 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 12 Jun 2025 13:53:41 +0100 Subject: [PATCH] Fix status trailers in exception handler --- .../grpc/sample/GrpcServerApplication.java | 8 +++++++- .../grpc/sample/GrpcServerIntegrationTests.java | 14 ++++++++------ .../exception/GrpcExceptionHandledServerCall.java | 1 + .../exception/GrpcExceptionHandlerInterceptor.java | 2 +- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/samples/grpc-server/src/main/java/org/springframework/grpc/sample/GrpcServerApplication.java b/samples/grpc-server/src/main/java/org/springframework/grpc/sample/GrpcServerApplication.java index 3a717b2..a85be96 100644 --- a/samples/grpc-server/src/main/java/org/springframework/grpc/sample/GrpcServerApplication.java +++ b/samples/grpc-server/src/main/java/org/springframework/grpc/sample/GrpcServerApplication.java @@ -5,7 +5,9 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.grpc.server.exception.GrpcExceptionHandler; +import io.grpc.Metadata; import io.grpc.Status; +import io.grpc.StatusException; @SpringBootApplication public class GrpcServerApplication { @@ -18,7 +20,11 @@ public class GrpcServerApplication { public GrpcExceptionHandler globalInterceptor() { return exception -> { if (exception instanceof IllegalArgumentException) { - return Status.INVALID_ARGUMENT.withDescription(exception.getMessage()).asException(); + Metadata metadata = new Metadata(); + metadata.put(Metadata.Key.of("error-code", Metadata.ASCII_STRING_MARSHALLER), "INVALID_ARGUMENT"); + StatusException result = Status.INVALID_ARGUMENT.withDescription(exception.getMessage()) + .asException(metadata); + return result; } return null; }; diff --git a/samples/grpc-server/src/test/java/org/springframework/grpc/sample/GrpcServerIntegrationTests.java b/samples/grpc-server/src/test/java/org/springframework/grpc/sample/GrpcServerIntegrationTests.java index 17c7dc6..d173189 100644 --- a/samples/grpc-server/src/test/java/org/springframework/grpc/sample/GrpcServerIntegrationTests.java +++ b/samples/grpc-server/src/test/java/org/springframework/grpc/sample/GrpcServerIntegrationTests.java @@ -44,6 +44,7 @@ import org.springframework.test.context.ActiveProfiles; import io.grpc.ForwardingServerCallListener; import io.grpc.ManagedChannel; +import io.grpc.Metadata; import io.grpc.ServerCall.Listener; import io.grpc.ServerInterceptor; import io.grpc.Status.Code; @@ -77,18 +78,19 @@ class GrpcServerIntegrationTests { void specificErrorResponse(@Autowired GrpcChannelFactory channels) { SimpleGrpc.SimpleBlockingStub client = SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:0")); assertThat(assertThrows(StatusRuntimeException.class, - () -> client.sayHello(HelloRequest.newBuilder().setName("error").build())) + () -> client.sayHello(HelloRequest.newBuilder().setName("internal").build())) .getStatus() - .getCode()).isEqualTo(Code.INVALID_ARGUMENT); + .getCode()).isEqualTo(Code.UNKNOWN); } @Test void defaultErrorResponseIsUnknown(@Autowired GrpcChannelFactory channels) { SimpleGrpc.SimpleBlockingStub client = SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:0")); - assertThat(assertThrows(StatusRuntimeException.class, - () -> client.sayHello(HelloRequest.newBuilder().setName("internal").build())) - .getStatus() - .getCode()).isEqualTo(Code.UNKNOWN); + StatusRuntimeException status = assertThrows(StatusRuntimeException.class, + () -> client.sayHello(HelloRequest.newBuilder().setName("error").build())); + assertThat(status.getStatus().getCode()).isEqualTo(Code.INVALID_ARGUMENT); + assertThat(status.getTrailers().get(Metadata.Key.of("error-code", Metadata.ASCII_STRING_MARSHALLER))) + .isNotNull(); } } diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandledServerCall.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandledServerCall.java index d4d073d..2aef184 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandledServerCall.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandledServerCall.java @@ -36,6 +36,7 @@ public class GrpcExceptionHandledServerCall if (status.getCode() == Status.Code.UNKNOWN && status.getCause() != null) { final Throwable cause = status.getCause(); final StatusException statusException = this.exceptionHandler.handleException(cause); + trailers.merge(statusException.getTrailers()); super.close(statusException.getStatus(), trailers); } else { diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandlerInterceptor.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandlerInterceptor.java index 6279c3e..9e07eff 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandlerInterceptor.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandlerInterceptor.java @@ -147,7 +147,7 @@ public class GrpcExceptionHandlerInterceptor implements ServerInterceptor { catch (Throwable e) { } try { - this.call.close(status.getStatus(), headers(t)); + this.call.close(status.getStatus(), headers(status)); } catch (Throwable e) { throw new IllegalStateException("Failed to close the call", e);