Skip to content

Commit 509d60a

Browse files
authored
Merge pull request #344
* Copy status from StatusRuntimeException if no handler method is prese…
1 parent 1ca3036 commit 509d60a

File tree

3 files changed

+145
-43
lines changed

3 files changed

+145
-43
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package org.lognet.springboot.grpc.recovery;
2+
3+
import io.grpc.Metadata;
4+
import io.grpc.Status;
5+
import io.grpc.StatusRuntimeException;
6+
import io.grpc.examples.custom.Custom;
7+
import io.grpc.examples.custom.CustomServiceGrpc;
8+
import io.grpc.stub.StreamObserver;
9+
import org.junit.Test;
10+
import org.junit.runner.RunWith;
11+
import org.lognet.springboot.grpc.GRpcService;
12+
import org.lognet.springboot.grpc.GrpcServerTestBase;
13+
import org.lognet.springboot.grpc.demo.DemoApp;
14+
import org.mockito.Mockito;
15+
import org.springframework.boot.test.context.SpringBootTest;
16+
import org.springframework.boot.test.context.TestConfiguration;
17+
import org.springframework.boot.test.mock.mockito.SpyBean;
18+
import org.springframework.context.annotation.Import;
19+
import org.springframework.test.context.ActiveProfiles;
20+
import org.springframework.test.context.junit4.SpringRunner;
21+
22+
import java.util.concurrent.CompletableFuture;
23+
import java.util.concurrent.ExecutionException;
24+
import java.util.concurrent.TimeUnit;
25+
import java.util.concurrent.TimeoutException;
26+
27+
import static org.hamcrest.MatcherAssert.assertThat;
28+
import static org.hamcrest.Matchers.is;
29+
import static org.hamcrest.Matchers.isA;
30+
import static org.hamcrest.Matchers.notNullValue;
31+
import static org.junit.jupiter.api.Assertions.assertThrows;
32+
import static org.mockito.ArgumentMatchers.any;
33+
import static org.mockito.Mockito.never;
34+
import static org.mockito.Mockito.times;
35+
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE;
36+
37+
@RunWith(SpringRunner.class)
38+
@SpringBootTest(classes = {DemoApp.class}, webEnvironment = NONE)
39+
@ActiveProfiles({"disable-security"})
40+
@Import(GRpcStatusRuntimeExceptionTest.Cfg.class)
41+
public class GRpcStatusRuntimeExceptionTest extends GrpcServerTestBase {
42+
43+
44+
@TestConfiguration
45+
static class Cfg {
46+
47+
@GRpcService
48+
static class CustomService extends CustomServiceGrpc.CustomServiceImplBase {
49+
50+
@Override
51+
public void custom(Custom.CustomRequest request, StreamObserver<Custom.CustomReply> responseObserver) {
52+
throw new StatusRuntimeException(Status.FAILED_PRECONDITION);
53+
}
54+
55+
@Override
56+
public StreamObserver<Custom.CustomRequest> customStream(StreamObserver<Custom.CustomReply> responseObserver) {
57+
throw new StatusRuntimeException(Status.FAILED_PRECONDITION);
58+
}
59+
60+
}
61+
62+
}
63+
64+
@Test
65+
public void streamingStatusRuntimeExceptionTest() throws ExecutionException, InterruptedException, TimeoutException {
66+
final CompletableFuture<Throwable> errorFuture = new CompletableFuture<>();
67+
final StreamObserver<Custom.CustomReply> reply = new StreamObserver<Custom.CustomReply>() {
68+
69+
@Override
70+
public void onNext(Custom.CustomReply value) {
71+
72+
}
73+
74+
@Override
75+
public void onError(Throwable t) {
76+
errorFuture.complete(t);
77+
}
78+
79+
@Override
80+
public void onCompleted() {
81+
errorFuture.complete(null);
82+
}
83+
};
84+
85+
final StreamObserver<Custom.CustomRequest> requests = CustomServiceGrpc.newStub(getChannel()).customStream(reply);
86+
requests.onNext(Custom.CustomRequest.newBuilder().build());
87+
requests.onCompleted();
88+
89+
final Throwable actual = errorFuture.get(20, TimeUnit.SECONDS);
90+
assertThat(actual, notNullValue());
91+
assertThat(actual, isA(StatusRuntimeException.class));
92+
assertThat(((StatusRuntimeException)actual).getStatus(), is(Status.FAILED_PRECONDITION));
93+
}
94+
95+
@Test
96+
public void statusRuntimeExceptionTest() {
97+
final StatusRuntimeException statusRuntimeException = assertThrows(StatusRuntimeException.class, () ->
98+
CustomServiceGrpc.newBlockingStub(getChannel()).custom(Custom.CustomRequest.newBuilder().build())
99+
);
100+
assertThat(statusRuntimeException.getStatus(), is(Status.FAILED_PRECONDITION));
101+
}
102+
103+
}

grpc-spring-boot-starter/src/main/java/org/lognet/springboot/grpc/FailureHandlingSupport.java

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.grpc.Metadata;
44
import io.grpc.ServerCall;
55
import io.grpc.Status;
6+
import io.grpc.StatusRuntimeException;
67
import lombok.extern.slf4j.Slf4j;
78
import org.lognet.springboot.grpc.recovery.GRpcExceptionHandlerMethodResolver;
89
import org.lognet.springboot.grpc.recovery.GRpcExceptionScope;
@@ -16,61 +17,64 @@ public class FailureHandlingSupport {
1617

1718
private final GRpcExceptionHandlerMethodResolver methodResolver;
1819

19-
20-
2120
public FailureHandlingSupport(GRpcExceptionHandlerMethodResolver methodResolver) {
2221
this.methodResolver = methodResolver;
2322
}
2423

25-
26-
27-
2824
public void closeCall(RuntimeException e, ServerCall<?, ?> call, Metadata headers) throws RuntimeException {
2925
closeCall(e,call,headers,null);
3026
}
3127

3228
public void closeCall( RuntimeException e, ServerCall<?, ?> call, Metadata headers, Consumer<GRpcExceptionScope.GRpcExceptionScopeBuilder> customizer) throws RuntimeException {
3329

34-
35-
36-
Status statusToSend = Status.INTERNAL;
37-
Metadata metadataToSend = null;
38-
39-
final Optional<HandlerMethod> handlerMethod = methodResolver.resolveMethodByThrowable(call.getMethodDescriptor().getServiceName(), e);
30+
if(e == null) {
31+
log.warn("Closing null exception with {}", Status.INTERNAL);
32+
call.close(Status.INTERNAL, new Metadata());
33+
} else {
34+
Throwable unwrapped = GRpcRuntimeExceptionWrapper.unwrap(e);
35+
final Optional<HandlerMethod> handlerMethod = methodResolver.resolveMethodByThrowable(call.getMethodDescriptor().getServiceName(), unwrapped);
4036
if (handlerMethod.isPresent()) {
41-
final GRpcExceptionScope.GRpcExceptionScopeBuilder exceptionScopeBuilder = GRpcExceptionScope.builder()
42-
.callHeaders(headers)
43-
.methodCallAttributes(call.getAttributes())
44-
.methodDescriptor(call.getMethodDescriptor())
45-
.hint(GRpcRuntimeExceptionWrapper.getHint(e));
46-
Optional.ofNullable(customizer)
47-
.ifPresent(c -> c.accept(exceptionScopeBuilder));
48-
49-
final GRpcExceptionScope excScope = exceptionScopeBuilder.build();
37+
handle(handlerMethod.get(), call, customizer, e, headers, unwrapped);
38+
} else if (unwrapped instanceof StatusRuntimeException) {
39+
StatusRuntimeException sre = (StatusRuntimeException) unwrapped;
40+
log.warn("Closing call with {}", sre.getStatus());
41+
call.close(sre.getStatus(), Optional.ofNullable(sre.getTrailers()).orElseGet(Metadata::new));
42+
} else {
43+
log.warn("Closing call with {}", Status.INTERNAL);
44+
call.close(Status.INTERNAL, new Metadata());
45+
}
46+
}
47+
}
5048

51-
final HandlerMethod handler = handlerMethod.get();
49+
private void handle(HandlerMethod handler, ServerCall<?, ?> call, Consumer<GRpcExceptionScope.GRpcExceptionScopeBuilder> customizer, RuntimeException e, Metadata headers, Throwable unwrapped) {
50+
final GRpcExceptionScope.GRpcExceptionScopeBuilder exceptionScopeBuilder = GRpcExceptionScope.builder()
51+
.callHeaders(headers)
52+
.methodCallAttributes(call.getAttributes())
53+
.methodDescriptor(call.getMethodDescriptor())
54+
.hint(GRpcRuntimeExceptionWrapper.getHint(e));
5255

53-
try {
54-
statusToSend = handler.invoke(GRpcRuntimeExceptionWrapper.unwrap(e), excScope);
55-
metadataToSend = excScope.getResponseHeaders();
56-
} catch (Exception handlerException) {
56+
if(customizer != null) {
57+
customizer.accept(exceptionScopeBuilder);
58+
}
5759

58-
org.slf4j.LoggerFactory.getLogger(this.getClass())
59-
.error("Caught exception while executing handler method {}, returning {} status.",
60-
handler.getMethod(),
61-
statusToSend,
62-
handlerException);
60+
final GRpcExceptionScope excScope = exceptionScopeBuilder.build();
6361

64-
}
65-
}
62+
try {
63+
Status statusToSend = handler.invoke(unwrapped, excScope);
64+
Metadata metadataToSend = excScope.getResponseHeaders();
6665

67-
log.warn("Closing call with {}",statusToSend,GRpcRuntimeExceptionWrapper.unwrap(e));
66+
log.warn("Handled exception {} call as {}", unwrapped.getClass().getSimpleName(), statusToSend);
6867
call.close(statusToSend, Optional.ofNullable(metadataToSend).orElseGet(Metadata::new));
69-
70-
68+
} catch (Exception handlerException) {
69+
org.slf4j.LoggerFactory.getLogger(this.getClass())
70+
.error("Caught exception while handling exception {} using method {}, closing with {}.",
71+
unwrapped.getClass().getSimpleName(),
72+
handler.getMethod(),
73+
Status.INTERNAL,
74+
handlerException);
75+
call.close(Status.INTERNAL, new Metadata());
76+
}
7177
}
7278

7379

74-
75-
7680
}

grpc-spring-boot-starter/src/main/java/org/lognet/springboot/grpc/recovery/GRpcExceptionHandlerMethodResolver.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,7 @@ private GRpcExceptionHandlerMethodResolver(Collection<Object> advices) {
6464
}
6565

6666

67-
public Optional<HandlerMethod> resolveMethodByThrowable(String grpcServiceName, Throwable exc) {
68-
if(null==exc){
69-
return Optional.empty();
70-
}
71-
Throwable exception = GRpcRuntimeExceptionWrapper.unwrap(exc);
72-
67+
public Optional<HandlerMethod> resolveMethodByThrowable(String grpcServiceName, Throwable exception) {
7368
Optional<HandlerMethod> method = Optional.ofNullable(privateResolvers)
7469
.map(r -> r.get(grpcServiceName))
7570
.flatMap(r -> r.resolveMethodByThrowable(grpcServiceName, exception));

0 commit comments

Comments
 (0)