Skip to content

Commit 1f76160

Browse files
authored
Merge pull request #586 from yidongnan/feature/streaming-exception-handling
2 parents 03c321d + 11d10c1 commit 1f76160

File tree

13 files changed

+751
-308
lines changed

13 files changed

+751
-308
lines changed

grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/advice/GrpcAdvice.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@
2727

2828
/**
2929
* Special {@link Component @Component} to declare global gRPC exception handling.
30-
*
30+
*
31+
* <p>
3132
* Every class annotated with {@link GrpcAdvice @GrpcAdvice} is marked to be scanned for
3233
* {@link GrpcExceptionHandler @GrpcExceptionHandler} annotations.
33-
* <p>
34-
*
34+
* </p>
35+
*
3536
* @author Andjelko Perisic ([email protected])
3637
* @see GrpcExceptionHandler
3738
*/

grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/advice/GrpcAdviceExceptionHandler.java

Lines changed: 89 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
package net.devh.boot.grpc.server.advice;
1919

20+
import static java.util.Objects.requireNonNull;
21+
2022
import java.lang.reflect.InvocationTargetException;
2123
import java.lang.reflect.Method;
2224
import java.lang.reflect.Parameter;
@@ -25,68 +27,128 @@
2527

2628
import org.springframework.lang.Nullable;
2729

30+
import io.grpc.Metadata;
31+
import io.grpc.ServerCall;
32+
import io.grpc.Status;
33+
import io.grpc.StatusException;
34+
import io.grpc.StatusRuntimeException;
2835
import lombok.extern.slf4j.Slf4j;
36+
import net.devh.boot.grpc.server.error.GrpcExceptionInterceptor;
37+
import net.devh.boot.grpc.server.error.GrpcExceptionResponseHandler;
2938

3039
/**
3140
* As part of {@link GrpcAdvice @GrpcAdvice}, when a thrown exception is caught during gRPC calls (via global
32-
* interceptor {@link GrpcAdviceExceptionInterceptor}, then this thrown exception is being handled. By
33-
* {@link GrpcExceptionHandlerMethodResolver} is a mapping between exception and the in case to be executed method
34-
* provided. <br>
35-
* Returned object is declared in {@link GrpcAdvice @GrpcAdvice} classes with annotated methods
36-
* {@link GrpcExceptionHandler @GrpcExceptionHandler}.
41+
* interceptor {@link GrpcExceptionInterceptor}), then this thrown exception is being handled. The
42+
* {@link GrpcExceptionHandlerMethodResolver} provides a mapping for exceptions and their respective handler methods.
43+
*
3744
* <p>
45+
* The response is derived from methods annotated with {@link GrpcExceptionHandler} inside {@link GrpcAdvice} beans.
46+
* </p>
3847
*
3948
* @author Andjelko Perisic ([email protected])
49+
* @see GrpcAdvice
50+
* @see GrpcExceptionHandler
4051
* @see GrpcExceptionHandlerMethodResolver
41-
* @see GrpcAdviceExceptionInterceptor
52+
* @see GrpcExceptionInterceptor
4253
*/
4354
@Slf4j
44-
public class GrpcAdviceExceptionHandler {
55+
public class GrpcAdviceExceptionHandler implements GrpcExceptionResponseHandler {
4556

4657
private final GrpcExceptionHandlerMethodResolver grpcExceptionHandlerMethodResolver;
4758

59+
/**
60+
* Creates a new {@link GrpcAdvice} powered {@link GrpcExceptionHandler}.
61+
*
62+
* @param grpcExceptionHandlerMethodResolver The method resolver to use.
63+
*/
4864
public GrpcAdviceExceptionHandler(
4965
final GrpcExceptionHandlerMethodResolver grpcExceptionHandlerMethodResolver) {
50-
this.grpcExceptionHandlerMethodResolver = grpcExceptionHandlerMethodResolver;
66+
this.grpcExceptionHandlerMethodResolver =
67+
requireNonNull(grpcExceptionHandlerMethodResolver, "grpcExceptionHandlerMethodResolver");
68+
}
69+
70+
@Override
71+
public void handleError(final ServerCall<?, ?> serverCall, final Throwable error) {
72+
try {
73+
final Object mappedReturnType = handleThrownException(error);
74+
final Status status = resolveStatus(mappedReturnType);
75+
final Metadata metadata = resolveMetadata(mappedReturnType);
76+
77+
serverCall.close(status, metadata);
78+
} catch (final Throwable errorWhileResolving) {
79+
if (errorWhileResolving != error) {
80+
errorWhileResolving.addSuppressed(error);
81+
}
82+
handleThrownExceptionByImplementation(serverCall, errorWhileResolving);
83+
}
84+
}
85+
86+
protected Status resolveStatus(final Object mappedReturnType) {
87+
if (mappedReturnType instanceof Status) {
88+
return (Status) mappedReturnType;
89+
} else if (mappedReturnType instanceof Throwable) {
90+
return Status.fromThrowable((Throwable) mappedReturnType);
91+
}
92+
throw new IllegalStateException(String.format(
93+
"Error for mapped return type [%s] inside @GrpcAdvice, it has to be of type: "
94+
+ "[Status, StatusException, StatusRuntimeException, Throwable] ",
95+
mappedReturnType));
96+
}
97+
98+
protected Metadata resolveMetadata(final Object mappedReturnType) {
99+
Metadata result = null;
100+
if (mappedReturnType instanceof StatusException) {
101+
final StatusException statusException = (StatusException) mappedReturnType;
102+
result = statusException.getTrailers();
103+
} else if (mappedReturnType instanceof StatusRuntimeException) {
104+
final StatusRuntimeException statusException = (StatusRuntimeException) mappedReturnType;
105+
result = statusException.getTrailers();
106+
}
107+
return (result == null) ? new Metadata() : result;
108+
}
109+
110+
protected void handleThrownExceptionByImplementation(final ServerCall<?, ?> serverCall, final Throwable throwable) {
111+
log.error("Exception thrown during invocation of annotated @GrpcExceptionHandler method: ", throwable);
112+
serverCall.close(Status.INTERNAL.withCause(throwable)
113+
.withDescription("There was a server error trying to handle an exception"), new Metadata());
51114
}
52115

53116
/**
54117
* Given an exception, a lookup is performed to retrieve mapped method. <br>
55118
* In case of successful returned method, and matching exception parameter type for given exception, the exception
56119
* is handed over to retrieved method. Retrieved method is then being invoked.
57-
*
120+
*
58121
* @param exception exception to search for
59-
* @param <E> type of exception
60122
* @return result of invoked mapped method to given exception
61123
* @throws Throwable rethrows exception if no mapping existent or exceptions raised by implementation
62124
*/
63125
@Nullable
64-
public <E extends Throwable> Object handleThrownException(E exception) throws Throwable {
126+
protected Object handleThrownException(final Throwable exception) throws Throwable {
65127
log.debug("Exception caught during gRPC execution: ", exception);
66128

67129
final Class<? extends Throwable> exceptionClass = exception.getClass();
68-
boolean exceptionIsMapped =
69-
grpcExceptionHandlerMethodResolver.isMethodMappedForException(exceptionClass);
130+
final boolean exceptionIsMapped =
131+
this.grpcExceptionHandlerMethodResolver.isMethodMappedForException(exceptionClass);
70132
if (!exceptionIsMapped) {
71133
throw exception;
72134
}
73135

74-
Entry<Object, Method> methodWithInstance =
75-
grpcExceptionHandlerMethodResolver.resolveMethodWithInstance(exceptionClass);
76-
Method mappedMethod = methodWithInstance.getValue();
77-
Object instanceOfMappedMethod = methodWithInstance.getKey();
78-
Object[] instancedParams = determineInstancedParameters(mappedMethod, exception);
136+
final Entry<Object, Method> methodWithInstance =
137+
this.grpcExceptionHandlerMethodResolver.resolveMethodWithInstance(exceptionClass);
138+
final Method mappedMethod = methodWithInstance.getValue();
139+
final Object instanceOfMappedMethod = methodWithInstance.getKey();
140+
final Object[] instancedParams = determineInstancedParameters(mappedMethod, exception);
79141

80142
return invokeMappedMethodSafely(mappedMethod, instanceOfMappedMethod, instancedParams);
81143
}
82144

83-
private <E extends Throwable> Object[] determineInstancedParameters(Method mappedMethod, E exception) {
145+
private Object[] determineInstancedParameters(final Method mappedMethod, final Throwable exception) {
84146

85-
Parameter[] parameters = mappedMethod.getParameters();
86-
Object[] instancedParams = new Object[parameters.length];
147+
final Parameter[] parameters = mappedMethod.getParameters();
148+
final Object[] instancedParams = new Object[parameters.length];
87149

88150
for (int i = 0; i < parameters.length; i++) {
89-
Class<?> parameterClass = convertToClass(parameters[i]);
151+
final Class<?> parameterClass = convertToClass(parameters[i]);
90152
if (parameterClass.isAssignableFrom(exception.getClass())) {
91153
instancedParams[i] = exception;
92154
break;
@@ -95,18 +157,18 @@ private <E extends Throwable> Object[] determineInstancedParameters(Method mappe
95157
return instancedParams;
96158
}
97159

98-
private Class<?> convertToClass(Parameter parameter) {
99-
Type paramType = parameter.getParameterizedType();
160+
private Class<?> convertToClass(final Parameter parameter) {
161+
final Type paramType = parameter.getParameterizedType();
100162
if (paramType instanceof Class) {
101163
return (Class<?>) paramType;
102164
}
103165
throw new IllegalStateException("Parameter type of method has to be from Class, it was: " + paramType);
104166
}
105167

106168
private Object invokeMappedMethodSafely(
107-
Method mappedMethod,
108-
Object instanceOfMappedMethod,
109-
Object[] instancedParams) throws Throwable {
169+
final Method mappedMethod,
170+
final Object instanceOfMappedMethod,
171+
final Object[] instancedParams) throws Throwable {
110172
try {
111173
return mappedMethod.invoke(instanceOfMappedMethod, instancedParams);
112174
} catch (InvocationTargetException | IllegalAccessException e) {

grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/advice/GrpcAdviceExceptionListener.java

Lines changed: 0 additions & 108 deletions
This file was deleted.

grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcAdviceAutoConfiguration.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@
2727
import net.devh.boot.grpc.server.advice.GrpcAdvice;
2828
import net.devh.boot.grpc.server.advice.GrpcAdviceDiscoverer;
2929
import net.devh.boot.grpc.server.advice.GrpcAdviceExceptionHandler;
30-
import net.devh.boot.grpc.server.advice.GrpcAdviceExceptionInterceptor;
3130
import net.devh.boot.grpc.server.advice.GrpcAdviceIsPresentCondition;
3231
import net.devh.boot.grpc.server.advice.GrpcExceptionHandler;
3332
import net.devh.boot.grpc.server.advice.GrpcExceptionHandlerMethodResolver;
33+
import net.devh.boot.grpc.server.error.GrpcExceptionInterceptor;
3434
import net.devh.boot.grpc.server.interceptor.GrpcGlobalServerInterceptor;
3535

3636
/**
@@ -44,7 +44,7 @@
4444
* @author Andjelko Perisic ([email protected])
4545
* @see GrpcAdvice
4646
* @see GrpcExceptionHandler
47-
* @see GrpcAdviceExceptionInterceptor
47+
* @see GrpcExceptionInterceptor
4848
*/
4949
@Configuration(proxyBeanMethods = false)
5050
@Conditional(GrpcAdviceIsPresentCondition.class)
@@ -70,9 +70,9 @@ public GrpcAdviceExceptionHandler grpcAdviceExceptionHandler(
7070

7171
@GrpcGlobalServerInterceptor
7272
@Order(InterceptorOrder.ORDER_GLOBAL_EXCEPTION_HANDLING)
73-
public GrpcAdviceExceptionInterceptor grpcAdviceExceptionInterceptor(
73+
public GrpcExceptionInterceptor grpcAdviceExceptionInterceptor(
7474
GrpcAdviceExceptionHandler grpcAdviceExceptionHandler) {
75-
return new GrpcAdviceExceptionInterceptor(grpcAdviceExceptionHandler);
75+
return new GrpcExceptionInterceptor(grpcAdviceExceptionHandler);
7676
}
7777

7878
}

0 commit comments

Comments
 (0)