Skip to content

Commit

Permalink
Merge pull request #586 from yidongnan/feature/streaming-exception-ha…
Browse files Browse the repository at this point in the history
…ndling
  • Loading branch information
yidongnan committed Sep 24, 2021
2 parents 03c321d + 11d10c1 commit 1f76160
Show file tree
Hide file tree
Showing 13 changed files with 751 additions and 308 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@

/**
* Special {@link Component @Component} to declare global gRPC exception handling.
*
*
* <p>
* Every class annotated with {@link GrpcAdvice @GrpcAdvice} is marked to be scanned for
* {@link GrpcExceptionHandler @GrpcExceptionHandler} annotations.
* <p>
*
* </p>
*
* @author Andjelko Perisic (andjelko.perisic@gmail.com)
* @see GrpcExceptionHandler
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

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

import static java.util.Objects.requireNonNull;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
Expand All @@ -25,68 +27,128 @@

import org.springframework.lang.Nullable;

import io.grpc.Metadata;
import io.grpc.ServerCall;
import io.grpc.Status;
import io.grpc.StatusException;
import io.grpc.StatusRuntimeException;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.server.error.GrpcExceptionInterceptor;
import net.devh.boot.grpc.server.error.GrpcExceptionResponseHandler;

/**
* As part of {@link GrpcAdvice @GrpcAdvice}, when a thrown exception is caught during gRPC calls (via global
* interceptor {@link GrpcAdviceExceptionInterceptor}, then this thrown exception is being handled. By
* {@link GrpcExceptionHandlerMethodResolver} is a mapping between exception and the in case to be executed method
* provided. <br>
* Returned object is declared in {@link GrpcAdvice @GrpcAdvice} classes with annotated methods
* {@link GrpcExceptionHandler @GrpcExceptionHandler}.
* interceptor {@link GrpcExceptionInterceptor}), then this thrown exception is being handled. The
* {@link GrpcExceptionHandlerMethodResolver} provides a mapping for exceptions and their respective handler methods.
*
* <p>
* The response is derived from methods annotated with {@link GrpcExceptionHandler} inside {@link GrpcAdvice} beans.
* </p>
*
* @author Andjelko Perisic (andjelko.perisic@gmail.com)
* @see GrpcAdvice
* @see GrpcExceptionHandler
* @see GrpcExceptionHandlerMethodResolver
* @see GrpcAdviceExceptionInterceptor
* @see GrpcExceptionInterceptor
*/
@Slf4j
public class GrpcAdviceExceptionHandler {
public class GrpcAdviceExceptionHandler implements GrpcExceptionResponseHandler {

private final GrpcExceptionHandlerMethodResolver grpcExceptionHandlerMethodResolver;

/**
* Creates a new {@link GrpcAdvice} powered {@link GrpcExceptionHandler}.
*
* @param grpcExceptionHandlerMethodResolver The method resolver to use.
*/
public GrpcAdviceExceptionHandler(
final GrpcExceptionHandlerMethodResolver grpcExceptionHandlerMethodResolver) {
this.grpcExceptionHandlerMethodResolver = grpcExceptionHandlerMethodResolver;
this.grpcExceptionHandlerMethodResolver =
requireNonNull(grpcExceptionHandlerMethodResolver, "grpcExceptionHandlerMethodResolver");
}

@Override
public void handleError(final ServerCall<?, ?> serverCall, final Throwable error) {
try {
final Object mappedReturnType = handleThrownException(error);
final Status status = resolveStatus(mappedReturnType);
final Metadata metadata = resolveMetadata(mappedReturnType);

serverCall.close(status, metadata);
} catch (final Throwable errorWhileResolving) {
if (errorWhileResolving != error) {
errorWhileResolving.addSuppressed(error);
}
handleThrownExceptionByImplementation(serverCall, errorWhileResolving);
}
}

protected Status resolveStatus(final Object mappedReturnType) {
if (mappedReturnType instanceof Status) {
return (Status) mappedReturnType;
} else if (mappedReturnType instanceof Throwable) {
return Status.fromThrowable((Throwable) mappedReturnType);
}
throw new IllegalStateException(String.format(
"Error for mapped return type [%s] inside @GrpcAdvice, it has to be of type: "
+ "[Status, StatusException, StatusRuntimeException, Throwable] ",
mappedReturnType));
}

protected Metadata resolveMetadata(final Object mappedReturnType) {
Metadata result = null;
if (mappedReturnType instanceof StatusException) {
final StatusException statusException = (StatusException) mappedReturnType;
result = statusException.getTrailers();
} else if (mappedReturnType instanceof StatusRuntimeException) {
final StatusRuntimeException statusException = (StatusRuntimeException) mappedReturnType;
result = statusException.getTrailers();
}
return (result == null) ? new Metadata() : result;
}

protected void handleThrownExceptionByImplementation(final ServerCall<?, ?> serverCall, final Throwable throwable) {
log.error("Exception thrown during invocation of annotated @GrpcExceptionHandler method: ", throwable);
serverCall.close(Status.INTERNAL.withCause(throwable)
.withDescription("There was a server error trying to handle an exception"), new Metadata());
}

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

final Class<? extends Throwable> exceptionClass = exception.getClass();
boolean exceptionIsMapped =
grpcExceptionHandlerMethodResolver.isMethodMappedForException(exceptionClass);
final boolean exceptionIsMapped =
this.grpcExceptionHandlerMethodResolver.isMethodMappedForException(exceptionClass);
if (!exceptionIsMapped) {
throw exception;
}

Entry<Object, Method> methodWithInstance =
grpcExceptionHandlerMethodResolver.resolveMethodWithInstance(exceptionClass);
Method mappedMethod = methodWithInstance.getValue();
Object instanceOfMappedMethod = methodWithInstance.getKey();
Object[] instancedParams = determineInstancedParameters(mappedMethod, exception);
final Entry<Object, Method> methodWithInstance =
this.grpcExceptionHandlerMethodResolver.resolveMethodWithInstance(exceptionClass);
final Method mappedMethod = methodWithInstance.getValue();
final Object instanceOfMappedMethod = methodWithInstance.getKey();
final Object[] instancedParams = determineInstancedParameters(mappedMethod, exception);

return invokeMappedMethodSafely(mappedMethod, instanceOfMappedMethod, instancedParams);
}

private <E extends Throwable> Object[] determineInstancedParameters(Method mappedMethod, E exception) {
private Object[] determineInstancedParameters(final Method mappedMethod, final Throwable exception) {

Parameter[] parameters = mappedMethod.getParameters();
Object[] instancedParams = new Object[parameters.length];
final Parameter[] parameters = mappedMethod.getParameters();
final Object[] instancedParams = new Object[parameters.length];

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

private Class<?> convertToClass(Parameter parameter) {
Type paramType = parameter.getParameterizedType();
private Class<?> convertToClass(final Parameter parameter) {
final Type paramType = parameter.getParameterizedType();
if (paramType instanceof Class) {
return (Class<?>) paramType;
}
throw new IllegalStateException("Parameter type of method has to be from Class, it was: " + paramType);
}

private Object invokeMappedMethodSafely(
Method mappedMethod,
Object instanceOfMappedMethod,
Object[] instancedParams) throws Throwable {
final Method mappedMethod,
final Object instanceOfMappedMethod,
final Object[] instancedParams) throws Throwable {
try {
return mappedMethod.invoke(instanceOfMappedMethod, instancedParams);
} catch (InvocationTargetException | IllegalAccessException e) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@
import net.devh.boot.grpc.server.advice.GrpcAdvice;
import net.devh.boot.grpc.server.advice.GrpcAdviceDiscoverer;
import net.devh.boot.grpc.server.advice.GrpcAdviceExceptionHandler;
import net.devh.boot.grpc.server.advice.GrpcAdviceExceptionInterceptor;
import net.devh.boot.grpc.server.advice.GrpcAdviceIsPresentCondition;
import net.devh.boot.grpc.server.advice.GrpcExceptionHandler;
import net.devh.boot.grpc.server.advice.GrpcExceptionHandlerMethodResolver;
import net.devh.boot.grpc.server.error.GrpcExceptionInterceptor;
import net.devh.boot.grpc.server.interceptor.GrpcGlobalServerInterceptor;

/**
Expand All @@ -44,7 +44,7 @@
* @author Andjelko Perisic (andjelko.perisic@gmail.com)
* @see GrpcAdvice
* @see GrpcExceptionHandler
* @see GrpcAdviceExceptionInterceptor
* @see GrpcExceptionInterceptor
*/
@Configuration(proxyBeanMethods = false)
@Conditional(GrpcAdviceIsPresentCondition.class)
Expand All @@ -70,9 +70,9 @@ public GrpcAdviceExceptionHandler grpcAdviceExceptionHandler(

@GrpcGlobalServerInterceptor
@Order(InterceptorOrder.ORDER_GLOBAL_EXCEPTION_HANDLING)
public GrpcAdviceExceptionInterceptor grpcAdviceExceptionInterceptor(
public GrpcExceptionInterceptor grpcAdviceExceptionInterceptor(
GrpcAdviceExceptionHandler grpcAdviceExceptionHandler) {
return new GrpcAdviceExceptionInterceptor(grpcAdviceExceptionHandler);
return new GrpcExceptionInterceptor(grpcAdviceExceptionHandler);
}

}

0 comments on commit 1f76160

Please sign in to comment.