Skip to content

Commit 0511a5c

Browse files
committed
WIP - initial code
1 parent f6f3b05 commit 0511a5c

File tree

12 files changed

+407
-3
lines changed

12 files changed

+407
-3
lines changed

docs/en/index.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# gRPC-Spring-Boot-Starter Documentation
22

33
gRPC-spring-boot-starter combines [google's open-source high performance RPC-framework](https://grpc.io) with
4-
[spring boot's ease of setup](https://spring.io/projects/spring-boot).
5-
This project simplifies the gRPC-server/client setup to adding one dependency to your project and adding a single
6-
annotation to your service class / client (stub) field.
4+
[spring boot's ease of setup](https://spring.io/projects/spring-boot).
5+
This project simplifies the gRPC-server/client setup to adding one dependency to your project and adding a single
6+
annotation to your service class / client (stub) field.
77
The features of this library are meant to complement your experience with gRPC and still allow you to do any
88
customization you need for your project.
99

@@ -12,6 +12,7 @@ customization you need for your project.
1212
- Server
1313
- [Getting Started](server/getting-started.md)
1414
- [Configuration](server/configuration.md)
15+
- [Exception Handling](server/exception-handling.md)
1516
- [Contextual Data / Scoped Beans](server/contextual-data.md)
1617
- [Testing the Service](server/testing.md)
1718
- [Security](server/security.md)

docs/en/server/configuration.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ This section describes how you can configure your grpc-spring-boot-starter appli
1717

1818
- [Getting Started](getting-started.md)
1919
- *Configuration*
20+
- [Exception Handling](exception-handling.md)
2021
- [Contextual Data / Scoped Beans](contextual-data.md)
2122
- [Testing the Service](testing.md)
2223
- [Security](security.md)
@@ -121,6 +122,7 @@ public GrpcServerConfigurer keepAliveServerConfigurer() {
121122

122123
- [Getting Started](getting-started.md)
123124
- *Configuration*
125+
- [Exception Handling](exception-handling.md)
124126
- [Contextual Data / Scoped Beans](contextual-data.md)
125127
- [Testing the Service](testing.md)
126128
- [Security](security.md)

docs/en/server/contextual-data.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ This section describes how you can store contextual / per request data.
1313

1414
- [Getting Started](getting-started.md)
1515
- [Configuration](configuration.md)
16+
- [Exception Handling](exception-handling.md)
1617
- *Contextual Data / Scoped Beans*
1718
- [Testing the Service](testing.md)
1819
- [Security](security.md)
@@ -61,6 +62,7 @@ public void grpcMethod(Request request, StreamObserver<Response> responseObserve
6162

6263
- [Getting Started](getting-started.md)
6364
- [Configuration](configuration.md)
65+
- [Exception Handling](exception-handling.md)
6466
- *Contextual Data / Scoped Beans*
6567
- [Testing the Service](testing.md)
6668
- [Security](security.md)

docs/en/server/exception-handling.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Exception Handling inside GrpcService
2+
3+
[<- Back to Index](../index.md)
4+
5+
This section describes how you can handle exceptions inside GrpcService layer without cluttering up your code.
6+
7+
## Table of Contents <!-- omit in toc -->
8+
9+
- [A Word of Warning](#a-word-of-warning)
10+
- [grpcRequest Scope](#grpcrequest-scope)
11+
12+
## Additional Topics <!-- omit in toc -->
13+
14+
- [Getting Started](getting-started.md)
15+
- [Configuration](configuration.md)
16+
- [Contextual Data](contextual-data.md)
17+
- *Exception Handling*
18+
- [Testing the Service](testing.md)
19+
- [Security](security.md)
20+
21+
## TODO ...
22+
23+
> TODO ...
24+
25+
## Additional Topics <!-- omit in toc -->
26+
27+
- [Getting Started](getting-started.md)
28+
- [Configuration](configuration.md)
29+
- [Contextual Data](contextual-data.md)
30+
- *Exception Handling*
31+
- [Testing the Service](testing.md)
32+
- [Security](security.md)
33+
34+
----------
35+
36+
[<- Back to Index](../index.md)

docs/en/server/getting-started.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ This section describes the steps necessary to convert your application into a gr
1818

1919
- *Getting started*
2020
- [Configuration](configuration.md)
21+
- [Exception Handling](exception-handling.md)
2122
- [Contextual Data / Scoped Beans](contextual-data.md)
2223
- [Testing the Service](testing.md)
2324
- [Security](security.md)
@@ -316,6 +317,7 @@ See [here](testing.md#grpcurl) for `gRPCurl` example command output and addition
316317

317318
- *Getting Started*
318319
- [Configuration](configuration.md)
320+
- [Exception Handling](exception-handling.md)
319321
- [Contextual Data / Scoped Beans](contextual-data.md)
320322
- [Testing the Service](testing.md)
321323
- [Security](security.md)

docs/en/server/security.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ We strongly recommend enabling at least transport layer security.
1919

2020
- [Getting Started](getting-started.md)
2121
- [Configuration](configuration.md)
22+
- [Exception Handling](exception-handling.md)
2223
- [Contextual Data / Scoped Beans](contextual-data.md)
2324
- [Testing the Service](testing.md)
2425
- *Security*
@@ -279,6 +280,7 @@ public void methodX(Request request, StreamObserver<Response> responseObserver)
279280

280281
- [Getting Started](getting-started.md)
281282
- [Configuration](configuration.md)
283+
- [Exception Handling](exception-handling.md)
282284
- [Contextual Data / Scoped Beans](contextual-data.md)
283285
- [Testing the Service](testing.md)
284286
- *Security*

docs/en/server/testing.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Please refer to [Tests with Grpc-Stubs](../client/testing.md).
2222

2323
- [Getting Started](getting-started.md)
2424
- [Configuration](configuration.md)
25+
- [Exception Handling](exception-handling.md)
2526
- [Contextual Data / Scoped Beans](contextual-data.md)
2627
- *Testing the Service*
2728
- [Security](security.md)
@@ -368,6 +369,7 @@ For more information regarding `gRPCurl` please refer to their [official documen
368369
369370
- [Getting Started](getting-started.md)
370371
- [Configuration](configuration.md)
372+
- [Exception Handling](exception-handling.md)
371373
- [Contextual Data / Scoped Beans](contextual-data.md)
372374
- *Testing the Service*
373375
- [Security](security.md)

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ public GrpcServiceDiscoverer defaultGrpcServiceDiscoverer() {
108108
return new AnnotationGrpcServiceDiscoverer();
109109
}
110110

111+
// TODO eventually insert @Conditional
112+
111113
@ConditionalOnMissingBean
112114
@Bean
113115
public HealthStatusManager healthStatusManager() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Copyright (c) 2016-2020 Michael Zhang <[email protected]>
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5+
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
6+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
7+
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
8+
*
9+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
10+
* Software.
11+
*
12+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
13+
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
14+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
15+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16+
*/
17+
18+
package net.devh.boot.grpc.server.service.exceptionhandling;
19+
20+
import java.lang.reflect.InvocationTargetException;
21+
import java.lang.reflect.Method;
22+
import java.lang.reflect.Parameter;
23+
import java.util.Map.Entry;
24+
import java.util.Optional;
25+
26+
import org.aspectj.lang.JoinPoint;
27+
import org.aspectj.lang.annotation.AfterThrowing;
28+
import org.aspectj.lang.annotation.Aspect;
29+
import org.aspectj.lang.annotation.Pointcut;
30+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
31+
import org.springframework.stereotype.Component;
32+
33+
import io.grpc.stub.StreamObserver;
34+
import lombok.RequiredArgsConstructor;
35+
import lombok.extern.slf4j.Slf4j;
36+
37+
/**
38+
* Exception handling for thrown {@link RuntimeException} inside gRPC service classes, which implements
39+
* {@link io.grpc.BindableService}.
40+
*/
41+
@Slf4j
42+
@Aspect
43+
@Component
44+
@RequiredArgsConstructor
45+
@ConditionalOnBean(annotation = GrpcServiceAdvice.class)
46+
public class GrpcExceptionAspect {
47+
48+
private final GrpcExceptionHandlerMethodResolver exceptionHandlerMethodResolver;
49+
50+
private Throwable exception;
51+
private Method mappedMethod;
52+
private Object instanceOfMappedMethod;
53+
54+
55+
@Pointcut("within(@net.devh.boot.grpc.server.service.GrpcService *)")
56+
void grpcServiceAnnotatedPointcut() {}
57+
58+
@Pointcut("within(io.grpc.BindableService+)")
59+
void implementedBindableServicePointcut() {}
60+
61+
@AfterThrowing(
62+
pointcut = "grpcServiceAnnotatedPointcut() && implementedBindableServicePointcut()",
63+
throwing = "exception")
64+
public <E extends Throwable> void handleExceptionInsideGrpcService(JoinPoint joinPoint, E exception) {
65+
log.error("Runtimeexception caught during gRPC service execution: ", exception);
66+
this.exception = exception;
67+
68+
extractNecessaryInformation();
69+
Throwable throwable = invokeMappedMethodSafely();
70+
closeStreamObserverOnError(joinPoint.getArgs(), throwable);
71+
}
72+
73+
private void extractNecessaryInformation() {
74+
75+
final Class<? extends Throwable> exceptionClass = exception.getClass();
76+
77+
Entry<Object, Method> methodWithInstance =
78+
exceptionHandlerMethodResolver.resolveMethodWithInstance(exceptionClass);
79+
mappedMethod =
80+
Optional.of(methodWithInstance)
81+
.map(Entry::getValue)
82+
.orElseThrow(() -> new IllegalStateException(
83+
"No mapped method found for Exception " + exceptionClass));
84+
instanceOfMappedMethod =
85+
Optional.of(methodWithInstance)
86+
.map(Entry::getKey)
87+
.orElseThrow(() -> new IllegalStateException(
88+
" No mapped instance found for Exception " + exceptionClass));
89+
}
90+
91+
private Throwable invokeMappedMethodSafely() {
92+
try {
93+
Object[] instancedParams = determineInstancedParameters(mappedMethod);
94+
Object statusThrowable = mappedMethod.invoke(instanceOfMappedMethod, instancedParams);
95+
return castToThrowable(statusThrowable);
96+
} catch (InvocationTargetException | IllegalAccessException e) {
97+
throw new IllegalStateException("No mapped instance found for Exception " + exception);
98+
}
99+
}
100+
101+
private Object[] determineInstancedParameters(Method mappedMethod) {
102+
103+
Parameter[] parameters = mappedMethod.getParameters();
104+
Object[] instancedParams = new Object[parameters.length];
105+
106+
for (int i = 0; i < parameters.length; i++) {
107+
Class<?> parameterClass = convertToClass(parameters[i]);
108+
if (parameterClass.equals(exception.getClass())) {
109+
instancedParams[i] = exception;
110+
break;
111+
}
112+
}
113+
return instancedParams;
114+
}
115+
116+
private Class<?> convertToClass(Parameter parameter) {
117+
return Optional.of(parameter)
118+
.map(Parameter::getParameterizedType)
119+
.filter(type -> type instanceof Class)
120+
.map(type -> (Class<?>) type)
121+
.orElseThrow();
122+
}
123+
124+
private Throwable castToThrowable(Object statusThrowable) {
125+
return Optional.of(statusThrowable)
126+
.filter(thrbl -> thrbl instanceof Throwable)
127+
.map(thrbl -> (Throwable) thrbl)
128+
.orElseThrow(() -> new IllegalStateException(
129+
"Return type has to beo f type java.lang.Throable: " + statusThrowable));
130+
}
131+
132+
private void closeStreamObserverOnError(Object[] joinPointParams, Throwable throwable) {
133+
for (Object param : joinPointParams) {
134+
if (param instanceof StreamObserver) {
135+
StreamObserver<?> streamObserver = (StreamObserver<?>) param;
136+
streamObserver.onError(throwable);
137+
}
138+
}
139+
}
140+
141+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright (c) 2016-2020 Michael Zhang <[email protected]>
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5+
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
6+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
7+
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
8+
*
9+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
10+
* Software.
11+
*
12+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
13+
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
14+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
15+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16+
*/
17+
18+
package net.devh.boot.grpc.server.service.exceptionhandling;
19+
20+
import java.lang.annotation.Documented;
21+
import java.lang.annotation.ElementType;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.RetentionPolicy;
24+
import java.lang.annotation.Target;
25+
26+
@Documented
27+
@Target(ElementType.METHOD)
28+
@Retention(RetentionPolicy.RUNTIME)
29+
public @interface GrpcExceptionHandler {
30+
31+
/**
32+
* Exceptions handled by the annotated method. If empty, will default to any exceptions listed in the method
33+
* argument list.
34+
*/
35+
Class<? extends Throwable>[] value() default {};
36+
}

0 commit comments

Comments
 (0)