Skip to content

Commit f7b51f4

Browse files
author
Alexander Furer
committed
4.5.8 release
1 parent af841cf commit f7b51f4

File tree

6 files changed

+164
-45
lines changed

6 files changed

+164
-45
lines changed

README.adoc

Lines changed: 118 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -266,14 +266,16 @@ public class B implements ServerInterceptor{
266266
}
267267
----
268268

269-
The starter uses built-in interceptors to implement Spring `Security`, `Validation` and `Metrics` integration.
269+
The starter uses built-in interceptors to implement error handling, Spring `Security`, `Validation` and `Metrics` integration.
270270
Their order can also be controlled by below properties :
271271

272-
* `grpc.security.auth.interceptor-order` ( defaults to `Ordered.HIGHEST_PRECEDENCE`)
272+
* `grpc.security.auth.interceptor-order` ( defaults to `Ordered.HIGHEST_PRECEDENCE+1`)
273273
* `grpc.validation.interceptor-order` ( defaults to `Ordered.HIGHEST_PRECEDENCE+10`)
274274
* `grpc.metrics.interceptor-order` ( defaults to `Ordered.HIGHEST_PRECEDENCE+20`)
275275

276-
This gives you the ability to setup the desired order of built-in and your custom interceptors.
276+
This gives you the ability to set up the desired order of built-in and your custom interceptors.
277+
278+
Error handling interceptor has the highest precedence.
277279

278280
*Keep on reading !!! There is more*
279281

@@ -450,6 +452,112 @@ If you enable both `NettyServer` and `in-process` servers, the `configure` metho
450452
If you need to differentiate between the passed `serverBuilder` s, you can check the type. +
451453
This is the current limitation.
452454

455+
== Error handling
456+
457+
The starter registers the `GRpcExceptionHandlerInterceptor` which is responsible to propagate the service-thrown exception to the error handlers. +
458+
The error handling method could be registered by having `@GRpcServiceAdvice` annotated bean with methods annotated with `@GRpcExceptionHandler` annotations. +
459+
These are considered as `global` error handlers and the method with exception type parameter nearest by the type hierarchy to the thrown exception is invoked. +
460+
The signature of the error handler has to follow the below pattern:
461+
462+
|===
463+
|Return type |Parameter 1 |Parameter 2
464+
465+
|io.grpc.Status
466+
|any `Exception` type
467+
|GRpcExceptionScope
468+
469+
470+
471+
|===
472+
473+
474+
475+
[source,java]
476+
.Sample
477+
----
478+
@GRpcServiceAdvice
479+
class MyHandler1{
480+
@GRpcExceptionHandler
481+
public Status handle (MyCustomExcpetion exc, GRpcExceptionScope scope){
482+
483+
}
484+
@GRpcExceptionHandler
485+
public Status handle (IllegalArgumentException exc, GRpcExceptionScope scope){
486+
487+
}
488+
489+
}
490+
@GRpcServiceAdvice
491+
class MyHandler2 {
492+
@GRpcExceptionHandler
493+
public Status anotherHandler (NullPointerException npe,GRpcExceptionScope scope){
494+
495+
}
496+
}
497+
----
498+
499+
You can have as many `advice` beans and handler methods as you want as long as they don't interfere with each other and don't create handled exception type ambiguity.
500+
501+
The `grpc` service bean is also discovered for error handlers, having the higher precedence than global error handling methods discovered in `@GRpcServiceAdvice` beans. The service-level error handling methods are considered `private` and invoked only when the exception is thrown by *this* service:
502+
503+
[source,java]
504+
.Sample
505+
----
506+
class SomeException extends Exception{
507+
508+
}
509+
class SomeRuntimeException extends RuntimeException{
510+
511+
}
512+
513+
@GRpcService
514+
public class HelloService extends GreeterGrpc.GreeterImplBase{
515+
@Override
516+
public void sayHello(GreeterOuterClass.HelloRequest request, StreamObserver<GreeterOuterClass.HelloReply> responseObserver) {
517+
...
518+
throw new GRpcRuntimeExceptionWrapper(new SomeException()) ; <1>
519+
//or
520+
throw new GRpcRuntimeExceptionWrapper(new SomeException(),"myHint") <2>
521+
//or
522+
throw new SomeRuntimeException() <3>
523+
}
524+
@GRpcExceptionHandler
525+
public Status privateHandler (SomeException npe,GRpcExceptionScope scope){
526+
// INVOKED when thrown from HelloService service
527+
String myHint = scope.getHintAs(String.class); <4>
528+
scope.getResponseHeaders().put(Metadata.Key.of("custom", Metadata.ASCII_STRING_MARSHALLER), "Value"); <5>
529+
}
530+
@GRpcExceptionHandler
531+
public Status privateHandler (SomeRuntimeException npe,GRpcExceptionScope scope){
532+
// INVOKED when thrown from HelloService service
533+
534+
}
535+
}
536+
@GRpcServiceAdvice
537+
class MyHandler {
538+
@GRpcExceptionHandler
539+
public Status anotherHandler (SomeException npe,GRpcExceptionScope scope){
540+
// NOT INVOKED when thrown from HelloService service
541+
}
542+
@GRpcExceptionHandler
543+
public Status anotherHandler (SomeRuntimeException npe,GRpcExceptionScope scope){
544+
// NOT INVOKED when thrown from HelloService service
545+
}
546+
547+
}
548+
----
549+
<1> Because the nature of `grpc` service API that doesn't allow throwing checked exception, the special runtime exception type is provided to wrap the checked exception. It's then getting unwrapped when looking for the handler method.
550+
<2> When throwing the `GRpcRuntimeExceptionWrapper` exception, you can also pass the `hint` object which is then accessible from the `scope` object in `handler` method.
551+
<3> Runtime exception can be thrown as-is and doesn't need to be wrapped.
552+
<4> Obtain the hint object.
553+
<5> Send custom headers to the client.
554+
555+
Authentication failure is propagated via `AuthenticationException` and authorization failure - via `AccessDeniedException`.
556+
557+
Validation failure is propagated via `ConstraintViolationException`: for failed request - with `Status.INVALID_ARGUMENT` as a hint , and for failed response - with `Status.FAILED_PRECONDITION` as a hint.
558+
559+
The demo is link:grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/recovery/GRpcRecoveryTest.java[here]
560+
453561
== Implementing message validation
454562

455563
Thanks to https://beanvalidation.org/2.0/spec/[Bean Validation] configuration support via https://beanvalidation.org/2.0/spec/#xml[XML deployment descriptor] , it's possible to
@@ -514,7 +622,6 @@ Demo is https://github.com/LogNet/grpc-spring-boot-starter/blob/master/grpc-spri
514622

515623

516624

517-
By adding `GRpcErrorHandler` bean to your application, you get a chance to send your custom response headers. The error handler will be called with `Status.INVALID_ARGUMENT` and incoming request message that is failed.
518625

519626

520627
== GRPC response observer and Spring @Transactional caveats
@@ -577,6 +684,10 @@ class MyGrpcService extends ...{
577684
By following this approach you also decouple the transport layer and business logic that now can be tested separately.
578685

579686

687+
688+
689+
690+
580691
== Spring Security Integration
581692

582693
=== Setup
@@ -720,14 +831,6 @@ Starting from `4.5.6`, the `Authentication` object can also be obtained via stan
720831
final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
721832
----
722833

723-
=== Custom authentication failure handling
724-
725-
By adding `GRpcErrorHandler` bean to your application, you get a chance to provide your custom response headers. The error handler will be called with `Status.PERMISSION_DENIED/Status.UNAUTHENTICATED` (and incoming request message , if you set `grpc.security.auth.fail-fast` property to `false`). +
726-
The demo is link:grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/JwtRoleTest.java[here]
727-
728-
729-
730-
731834

732835
=== Client side configuration support
733836

@@ -809,11 +912,11 @@ Starting from version `3.3.0`, the starter will auto-register the running grpc s
809912

810913
The registered service name will be prefixed with `grpc-` ,i.e. `grpc-${spring.application.name}` to not interfere with standard registered web-service name if you choose to run both embedded `Grpc` and `Web` servers. +
811914

812-
Setting `spring.cloud.consul.discovery.register-health-check` to true will register GRPC health check service in Consul.
915+
Setting `spring.cloud.consul.discovery.register-health-check` to true will register GRPC health check service with Consul.
813916

814917
Tags could be set by defining `spring.cloud.consul.discovery.tags` property.
815918

816-
There are 3 supported registration modes :
919+
There are 4 supported registration modes :
817920

818921
. `SINGLE_SERVER_WITH_GLOBAL_CHECK` (default) +
819922
In this mode the running grpc server is registered as single service with single `grpc` check with empty `serviceId`. +
@@ -822,6 +925,7 @@ Please note that default implementation https://github.com/grpc/grpc-java/blob/b
822925
In this mode the running grpc server is registered as single service with check per each discovered `grpc` service.
823926
. `STANDALONE_SERVICES` +
824927
In this mode each discovered grpc service is registered as single service with single check. Each registered service is tagged by its own service name.
928+
. `NOOP` - no grpc services registered. This mode is usefull if you serve both `rest` and `grpc` services in your application, but for some reason, only `rest` services should be registered with Consul.
825929

826930
[source,yml]
827931
.You can control the desired mode from application.properties

ReleaseNotes.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
| Starter Version | gRPC versions |Spring Boot version
22
| -------------------- |:-------------:|:------------------:|
3+
| [4.5.8](#version-458)| 1.41.0 |2.5.0 |
34
| [4.5.7](#version-457)| 1.40.1 |2.5.0 |
45
| [4.5.6](#version-456)| 1.40.0 |2.5.0 |
56
| [4.5.5](#version-455)| 1.39.0 |2.5.0 |
@@ -26,6 +27,20 @@
2627
| [4.0.0](#version-400)| 1.32.1 |2.3.3.RELEASE |
2728
| [3.5.7](#version-357)| 1.31.1 |1.5.13.RELEASE |
2829

30+
# Version 4.5.8
31+
## :star: New Features
32+
33+
- Support NOOP consul registration strategy [#251](https://github.com/LogNet/grpc-spring-boot-starter/issues/251)
34+
- Global error handling support [#223](https://github.com/LogNet/grpc-spring-boot-starter/issues/223)
35+
36+
## :hammer: Dependency Upgrades
37+
38+
- Upgrade grpc to 1.41.0 [#252](https://github.com/LogNet/grpc-spring-boot-starter/issues/252)
39+
40+
## :watch: Deprecations
41+
42+
- `GRpcErrorHandler` is deprecated in favor of `@GRpcServiceAdvice` and `@GRpcExceptionHandler` annotations.
43+
2944
# Version 4.5.7
3045
## :star: New Features
3146

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ gradleErrorPronePluginVersion=2.0.2
55
errorProneVersion=2.7.1
66
lombokVersion=1.18.20
77

8-
version=4.5.8-SNAPSHOT
8+
version=4.5.8
99
group=io.github.lognet
1010
description=Spring Boot starter for Google RPC.
1111
gitHubUrl=https\://github.com/LogNet/grpc-spring-boot-starter

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

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,44 +19,47 @@ public FailureHandlingSupport(GRpcExceptionHandlerMethodResolver methodResolver)
1919
this.methodResolver = methodResolver;
2020
}
2121

22+
public void closeCall(RuntimeException e, ServerCall<?, ?> call, Metadata headers) throws RuntimeException {
23+
closeCall(e,call,headers,null);
24+
}
2225
public void closeCall(RuntimeException e, ServerCall<?, ?> call, Metadata headers, Consumer<GRpcExceptionScope.GRpcExceptionScopeBuilder> customizer) throws RuntimeException {
2326

2427

25-
final Optional<HandlerMethod> handlerMethod = methodResolver.resolveMethodByThrowable(call.getMethodDescriptor().getServiceName(), e);
26-
if (handlerMethod.isPresent()) {
27-
final GRpcExceptionScope.GRpcExceptionScopeBuilder exceptionScopeBuilder = GRpcExceptionScope.builder()
28-
.callHeaders(headers)
29-
.methodCallAttributes(call.getAttributes())
30-
.methodDescriptor(call.getMethodDescriptor())
31-
.hint(GRpcRuntimeExceptionWrapper.getHint(e));
32-
customizer.accept(exceptionScopeBuilder);
28+
final Optional<HandlerMethod> handlerMethod = methodResolver.resolveMethodByThrowable(call.getMethodDescriptor().getServiceName(), e);
29+
if (handlerMethod.isPresent()) {
30+
final GRpcExceptionScope.GRpcExceptionScopeBuilder exceptionScopeBuilder = GRpcExceptionScope.builder()
31+
.callHeaders(headers)
32+
.methodCallAttributes(call.getAttributes())
33+
.methodDescriptor(call.getMethodDescriptor())
34+
.hint(GRpcRuntimeExceptionWrapper.getHint(e));
35+
Optional.ofNullable(customizer)
36+
.ifPresent(c -> c.accept(exceptionScopeBuilder));
3337

34-
final GRpcExceptionScope excScope = exceptionScopeBuilder.build();
38+
final GRpcExceptionScope excScope = exceptionScopeBuilder.build();
3539

36-
final HandlerMethod handler = handlerMethod.get();
40+
final HandlerMethod handler = handlerMethod.get();
3741

3842

39-
Status statusToSend = Status.INTERNAL;
40-
try {
41-
statusToSend = handler.invoke(GRpcRuntimeExceptionWrapper.unwrap(e),excScope );
42-
}catch (Exception handlerException){
43+
Status statusToSend = Status.INTERNAL;
44+
try {
45+
statusToSend = handler.invoke(GRpcRuntimeExceptionWrapper.unwrap(e), excScope);
46+
} catch (Exception handlerException) {
4347

44-
org.slf4j.LoggerFactory.getLogger(this.getClass())
45-
.error("Caught exception while executing handler method {}, returning {} status.",
46-
handler.getMethod(),
47-
statusToSend,
48-
handlerException);
48+
org.slf4j.LoggerFactory.getLogger(this.getClass())
49+
.error("Caught exception while executing handler method {}, returning {} status.",
50+
handler.getMethod(),
51+
statusToSend,
52+
handlerException);
4953

50-
}
51-
call.close(statusToSend, excScope.getResponseHeaders());
54+
}
55+
call.close(statusToSend, excScope.getResponseHeaders());
5256

5357

54-
} else {
55-
call.close(Status.INTERNAL,new Metadata());
56-
}
58+
} else {
59+
call.close(Status.INTERNAL, new Metadata());
60+
}
5761

5862
}
5963

6064

61-
6265
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ public void onHalfClose() {
6767

6868
};
6969
} catch (RuntimeException e) {
70-
failureHandlingSupport.closeCall(e, errorHandlingCall, headers, (b) -> {
71-
});
70+
failureHandlingSupport.closeCall(e, errorHandlingCall, headers);
7271
}
7372
return new ServerCall.Listener<ReqT>() {
7473

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,7 @@ private Context setupGRpcSecurityContext(ServerCall<?, ?> call, CharSequence aut
188188
private <RespT, ReqT> ServerCall.Listener<ReqT> fail(ServerCallHandler<ReqT, RespT> next, ServerCall<ReqT, RespT> call, Metadata headers, RuntimeException exception) throws RuntimeException {
189189

190190
if (authCfg.isFailFast()) {
191-
failureHandlingSupport.closeCall(exception, call, headers, b -> {
192-
193-
});
191+
failureHandlingSupport.closeCall(exception, call, headers);
194192

195193
return new ServerCall.Listener<ReqT>() {
196194

0 commit comments

Comments
 (0)