lognet / grpc-spring-boot-starter Goto Github PK
View Code? Open in Web Editor NEWSpring Boot starter module for gRPC framework.
License: Apache License 2.0
Spring Boot starter module for gRPC framework.
License: Apache License 2.0
Motivation:
Change:
Result:
I am trying to deploy my application [ GRPC + Spring Boot ] in Kubernetes infra.
I have set up a GRPC server with Spring boot on our pods - and as suggested I am running the processes on 2 different ports [ GRPC (port - 8443)+ Spring Boot (port- 8080) ] but my VIP is pointing to port 8443 right now.
I am able to configure SSL using .useTransportSecurity in a standalone grpc server (standalone here I mean without spring boot)
Problem: If my VIP is pointing to the GRPC server process on the pods I need to offload SSL in GRPC server but I do not see details how to do that if I am running my grpc server as an embedded server with spring boot.
Even if I deploy the application and when I try to connect using GRPC client I am seeing below error in servers and clinet is getting disconnected.
Where can I find additional application.properties specific to grpc ? I could only find grpc.server.port, grpc.server.host
2017-05-29 10:11:30.549 INFO 12796 --- [ main] n.d.s.a.grpc.server.GrpcServerLifecycle : gRPC Server started, listening on address: 0.0.0.0, port: 8443
2017-05-29 10:11:30.971 INFO 12796 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (https)
2017-05-29 10:11:30.979 INFO 12796 --- [ main] c.a.IntentProcessorGroup1Application : Started IntentProcessorGroup1Application in 26.45 seconds (JVM running for 27.737)
2017-05-29 10:11:41.736 WARN 12796 --- [-worker-ELG-3-1] io.grpc.netty.NettyServerHandler : Connection Error
io.netty.handler.codec.http2.Http2Exception: HTTP/2 client preface string missing or corrupt. Hex dump for received bytes: 160301008e0100008a03039bda7533eee4bee56f87bd4a42
at io.netty.handler.codec.http2.Http2Exception.connectionError(Http2Exception.java:85) ~[netty-codec-http2-4.1.8.Final.jar:4.1.8.Final]
There is an issue on the spring-cloud-sleuth project that might be better addressed in this library. Now that spring-cloud-sleuth 2.x is using the brave tracing library, there are client/server interceptors for instrumenting gRPC calls. Since your project already has a concept of global, server-side interceptors via @GRpcGlobalInterceptor
, I think it would be possible to add auto-configuration to setup up the tracing.
For server-side interceptor:
@ConditionalOnClass(GrpcTracing.class)
@GRpcService
.For client-side interceptor:
@ConditionalOnClass(GrpcTracing.class)
@ConditionalOnMissingBean
that could then be configured to use the client-side interceptor. Then a client can just inject the builder and use that to create the managed channel.I have created a sample project that demonstrates spring-cloud-sleuth with grpc-spring-starter.
Spring Boot Version: 2.0.0.RELEASE
@GRpcGlobalInterceptor
beans, created by @Bean
inside @Configuration
, are ignored if there is a class with annotation @GRpcGlobalInterceptor
Example:
@GRpcGlobalInterceptor
class ComponentInterceptor implements ServerInterceptor {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
System.out.println("ComponentInterceptor");
return next.startCall(call, headers);
}
}
class FactoryBeanInterceptor implements ServerInterceptor {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
System.out.println("FactoryBeanInterceptor");
return next.startCall(call, headers);
}
}
@Configuration
class GrpcConfiguration {
@Bean
@GRpcGlobalInterceptor
FactoryBeanInterceptor factoryBeanInterceptor() {
return new FactoryBeanInterceptor();
}
}
If we run an app, only ComponentInterceptor
will be executed.
What is the difference to https://github.com/yidongnan/grpc-spring-boot-starter ?
Hiya!
I just discovered this independently from something I was working on. Would you be interested in getting similar functionalities into the main spring boot starters?
A few minor differences:
The last one is big for Maven users, where the getting started experience, with stub generation, will become super easy when using the preconfigured plugin from the parent POM.
spring-projects/spring-boot#5206
Thanks!
Hi,
I have a @configuration class that defines a couple of @GlobalInterceptor classes like
@GRpcGlobalInterceptor
static class SSLSessionInterceptor implements ServerInterceptor {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall, Metadata headers,
ServerCallHandler<ReqT, RespT> next) {
// ..............
}
I then added another one using the @bean style configuration (according to the documentation this is supported)
@Bean
public ServerInterceptor beanInterceptor() {
return new ServerInterceptor() {
@Override
public <ReqT, RespT> Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall, Metadata metadata,
ServerCallHandler<ReqT, RespT> serverCallHandler) {
System.out.println("#########################");
return serverCallHandler.startCall(serverCall, metadata);
}
};
}
But this last interceptor is never called, I am assuming it does not get registered correctly on the GrpcServer. Anyone else seen this ?
For existing Code Base and smooth integration it would be if advance when server side code first contract API can still be specified with/rebase on spring MVC anotations. Why not create proto files for clientd based on spring MVC enpoints an allow grpc server listen and converts spring MVC endpoints on server side without additional coding effort based on existing spring MVC projects.
Most advanced the grpc server runs besides existing SpringMvc servlet.
The 0.0.2 release is not tagged in git yet.
Current Gradle version being used in the project, 2.14.1, is falling behind quite a bit. I suggest that Gradle be updated to 4.6, which is the current version.
In GRpcServerRunner.java you use wildcard imports for io.grpc.* and java.util.*.
While these seem very convenient, they are very problematic and are considered bad practice.
You may want to consider changing those to explicit imports. This is a really simple task with any modern Java IDE which can easily support you with imports.
As reference, please take a look at this SO question/answer: https://stackoverflow.com/questions/147454/why-is-using-a-wild-card-with-a-java-import-statement-bad
I came across this on the grpc groups https://groups.google.com/d/msg/grpc-io/P5BFGoGxkbw/ozwwY9bDCQAJ and was wondering if GrpcServerRunner is doing enough by just calling shutdown
@Override
public void destroy() throws Exception {
log.info("Shutting down gRPC server ...");
server.getServices().forEach(def->healthStatusManager.clearStatus(def.getServiceDescriptor().getName()));
Optional.ofNullable(server).ifPresent(Server::shutdown);
log.info("gRPC server stopped.");
}
From the post:
server.shutdown(); // This simply prevents new RPCs
server.awaitTermination(5, SECONDS); // This is the grace time
server.shutdownNow(); // This kills all RPCs
// This needs some time to queue cancellation notifications.
// Note that terminated does not imply the application is done processing.
// It only means the server won't issue any more work to the application.
if (!server.awaitTermination(1, SECONDS)) {
log.log(WARNING, "For some reason the server didn't shutdown promptly");
}
// After the server is terminated, all work has at least been queued onto the server's executor.
// You can shutdown and wait on that executor if you want. You can then also clean up services.
cleanUpServices();
The currently used protobuf-gradle-plugin uses a feature in Gradle that is deprecated in Gradle 5.0. I suggest an upgrade of the plugin to 0.8.5 to get rid of the warning.
More info about the bug and fix: google/protobuf-gradle-plugin#172
Hey guys, first of all kudos for the great job you've done.
Actually, I'm trying to spin up a Spring Boot (2.0) Webflux server exposing some REST APIs through
netty. This works fine and now I would like to give an extra alternative adding gRPC to the game.
I've managed to expose it but via another port (8080 for REST API, 9090 for gRPC).
Maybe that's unfeasible since gRPC requires HTTP 2 and the REST APIs can work with HTTP 1, but would be great if both options share the same port.
Annotating a server implementation method with @Secured
annotation currently doesn't have any effect.
It would be nice if the @Secured
annotation is supported and a Status.PERMISSION_DENIED
error is sent if the Authentication
doesn't contain the proper authority.
@Component
public class AuthenticationInterceptor implements ServerInterceptor {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall, Metadata metadata, ServerCallHandler<ReqT, RespT> serverCallHandler) {
Authentication authentication = new UsernamePasswordAuthenticationToken("user", "user",
Collections.singletonList(new SimpleGrantedAuthority(AuthoritiesConstants.USER));
SecurityContextHolder.getContext().setAuthentication(authentication);
return serverCallHandler.startCall(serverCall, metadata);
}
}
@GRpcService(interceptors = { LogInterceptor.class, AuthenticationInterceptor.class })
public class GreeterService extends GreeterGrpc.GreeterImplBase {
@Override
@Secured({ "ROLE_ADMIN" })
public void sayHello(GreeterOuterClass.HelloRequest request, StreamObserver<GreeterOuterClass.HelloReply> responseObserver) {
String message = "Hello " + request.getName();
final GreeterOuterClass.HelloReply.Builder replyBuilder = GreeterOuterClass.HelloReply.newBuilder().setMessage(message);
responseObserver.onNext(replyBuilder.build());
responseObserver.onCompleted();
log.info("Returning " +message);
}
}
then stub.sayHello
should fail with Status.PERMISSION_DENIED
just download the source code and import into intellij idea 2017.2, get the compiler complain:
Error:(49, 9) java: 找不到符号
符号: 变量 log
位置: 类 org.lognet.springboot.grpc.GRpcServerRunner
line 49: log.info("Starting gRPC Server ...");
It doesn't look like you can have them spin up say, jetty for the actuator endpoints and the embedded GRPC server together. The GRPC one takes over.
Allow to use Reactive types like Reactor Flux
or RxJava Observable
instead of dealing direction with gRPC StreamObserver
would be awesome for the end user, and since gRPC supports Backpressure that would be a very good match.
Integration with single value types like Reactor Mono
or RxJava Single
would also be really useful.
Maybe @saturnism has some ideas about how to achieve this since this is one of the topic or his upcoming Spring I/O talk ...
Before I invoke RPC, I attach CallCredentials to my stub, so I can know who the current user is:
// PersonContext.java
Long personId;
String username;
// GrpcSender.java
ManagedChannel channel = ManagedChannelBuilder.forAddress( "127.0.0.1", 6565 )
.usePlaintext( true )
.build();
HasPermissionServiceFutureStub stub = HasPermissionServiceGrpc.newFutureStub( channel )
.withCallCredentials( new PersonContextCallCredentials( personContext ) );
stub.invoke( Pojo.newBuilder().setPersonId( 1L ).build() );
Is there a way I can access the CallCredentials metadata in my @GRpcService so I can verify that the personId
in the metadata is equal to the personId
in the Request Message POJO?
Alternatively, is there a way to access the Request Message POJO in my interceptor. Ideally, I'd do something like the following in my interceptCall
method:
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call,
Metadata headers,
ServerCallHandler<ReqT, RespT> next )
{
String currentPersonId = headers.get( PersonContextCallCredentials.METADATA_KEY_FOR_PERSON_ID );
String requestedPersonId = REQUEST_MESSAGE_POJO.getPersonId();
if ( currentPersonId != null && !currentPersonId.equals( requestedPersonId ) )
{
call.close( Status.PERMISSION_DENIED, new Metadata() );
}
return next.startCall( call, headers );
}
But I'm not sure how to access REQUEST_MESSAGE_POJO
.
Hi,
It seems that spring's @transactional has no effect when used in a @GrpcService class. This is probably expected but nevertheless surprising. Integrating with spring's tx interceptor mechanics could make a worthy addition to this starter.
(and no i don't have a PR ready)
how to use with maven
Please add LICENSE file
Hi,
I'd like to contribute to this project. Based on my first glance I would have a few build improvements that come to my mind. Please give some feedback on how you like to see contributions (e.g. create / discuss issues first, only one issue per PR... etc)
First, thanks for your efforts to develop gRPC support for Spring Boot. We are using both frameworks and think it is a good combination.
Are there any known issues running against the latest gRPC build? I see that protobuf 3.0.0 is now out and that there have been some recent dependency updates in the gRPC repository.
We plan on using grpc-spring-boot-starter with Gradle and are interested about your future plans. As we move forward we are happy to submit pull requests for any issues or improvements.
Best Regards,
David
I try to use grpc-spring-boot-starter with mongodb, and I choose spring-boot-starter-data-mongodb, but
it can't autowire to MongoRepository.
@GRpcService
public class IndicateService extends IndicateServiceGrpc.IndicateServiceImplBase {
@Autowired
DailyBarRepository dailyBarSearchRepository;
....
}
In order to test several gRPC components with each other, I have several Spring Boot projects with this starter. I need to ensure that I always set the grpc.port property to something unused whenever I create a new project.
Would it be possible to allow random port selection when specifying grpc.port = 0, like it is e.g. done for server.port to allow random ports for the embedded Tomcat/Jetty server?
I can only imagine that the service discovery example is then a bit more tricky.
Repo description incorrectly states "Spring Boot starter module for Google RPC".
Should be "for the gRPC framework."
Please help,
There is any idea how can i get headers or something context from request on client side?
its need for logging
Hello,
I have MyAuthInterceptor.class to put an identity into the context, and I want to access the identity from the context. Here UserMe identity = MyAuthInterceptor.USER_IDENTITY.get();
got error, refer to below code and error message:
@GRpcService(interceptors = {MyAuthInterceptor.class, LogInterceptor.class})
public class MyWordService extends MyWordGrpc.MyWordImplBase {
private final static Gson gson = new Gson();
private static final Logger logger = Logger.getLogger(MyWordService.class.getName());
@Autowired
private UserWordRepository userWordRepository;
@Override
public void list(ListRequest request, StreamObserver<MyWordResponse> responseObserver) {
// Access to identity.
UserMe identity = MyAuthInterceptor.USER_IDENTITY.get();
logger.info(String.format("list start identity=%s, request=[%s]", identity, gson.toJson(request)));
}
/** Interceptor that validates user's identity. */
class MyAuthInterceptor implements ServerInterceptor {
public static final Context.Key<UserMe> USER_IDENTITY
= Context.key("identity"); // "identity" is just for debugging
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call,
Metadata headers,
ServerCallHandler<ReqT, RespT> next) {
// You need to implement validateIdentity
UserMe identity = validateIdentity(headers);
if (identity == null) { // this is optional, depending on your needs
// Assume user not authenticated
call.close(Status.UNAUTHENTICATED.withDescription("some more info"),
new Metadata());
return new ServerCall.Listener() {};
}
Context context = Context.current().withValue(USER_IDENTITY, identity);
return Contexts.interceptCall(context, call, headers, next);
}
private UserMe validateIdentity(Metadata headers) {
return new UserMe("conan");
}
}
java.lang.IllegalStateException: Failed to execute CommandLineRunner
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:735) [spring-boot-1.5.6.RELEASE.jar:1.5.6.RELEASE]
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:716) [spring-boot-1.5.6.RELEASE.jar:1.5.6.RELEASE]
at org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:703) [spring-boot-1.5.6.RELEASE.jar:1.5.6.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:304) [spring-boot-1.5.6.RELEASE.jar:1.5.6.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118) [spring-boot-1.5.6.RELEASE.jar:1.5.6.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107) [spring-boot-1.5.6.RELEASE.jar:1.5.6.RELEASE]
at org.ditto.easyhan.ApplicationSpringBoot.main(ApplicationSpringBoot.java:72) [classes/:na]
Caused by: org.springframework.beans.factory.BeanCreationException: Failed to create interceptor instance.; nested exception is java.lang.IllegalAccessException: Class org.lognet.springboot.grpc.GRpcServerRunner can not access a member of class org.ditto.easyhan.grpc.MyAuthInterceptor with modifiers ""
at org.lognet.springboot.grpc.GRpcServerRunner.lambda$bindInterceptors$2(GRpcServerRunner.java:110) ~[grpc-spring-boot-starter-2.1.0.jar:na]
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) ~[na:1.8.0_121]
at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) ~[na:1.8.0_121]
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) ~[na:1.8.0_121]
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) ~[na:1.8.0_121]
at java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:312) ~[na:1.8.0_121]
at java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:743) ~[na:1.8.0_121]
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) ~[na:1.8.0_121]
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) ~[na:1.8.0_121]
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) ~[na:1.8.0_121]
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_121]
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499) ~[na:1.8.0_121]
at org.lognet.springboot.grpc.GRpcServerRunner.bindInterceptors(GRpcServerRunner.java:118) ~[grpc-spring-boot-starter-2.1.0.jar:na]
at org.lognet.springboot.grpc.GRpcServerRunner.lambda$run$1(GRpcServerRunner.java:82) ~[grpc-spring-boot-starter-2.1.0.jar:na]
at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) ~[na:1.8.0_121]
at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175) ~[na:1.8.0_121]
at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) ~[na:1.8.0_121]
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) ~[na:1.8.0_121]
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) ~[na:1.8.0_121]
at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) ~[na:1.8.0_121]
at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) ~[na:1.8.0_121]
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_121]
at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418) ~[na:1.8.0_121]
at org.lognet.springboot.grpc.GRpcServerRunner.run(GRpcServerRunner.java:78) ~[grpc-spring-boot-starter-2.1.0.jar:na]
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:732) [spring-boot-1.5.6.RELEASE.jar:1.5.6.RELEASE]
... 6 common frames omitted
Caused by: java.lang.IllegalAccessException: Class org.lognet.springboot.grpc.GRpcServerRunner can not access a member of class org.ditto.easyhan.grpc.MyAuthInterceptor with modifiers ""
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102) ~[na:1.8.0_121]
at java.lang.Class.newInstance(Class.java:436) ~[na:1.8.0_121]
at org.lognet.springboot.grpc.GRpcServerRunner.lambda$bindInterceptors$2(GRpcServerRunner.java:108) ~[grpc-spring-boot-starter-2.1.0.jar:na]
... 30 common frames omitted
exception
➜ grpc-spring-boot-starter git:(master) gradle build --info
Starting Build
Settings evaluated using settings file '/Users/leo/Documents/git/grpc-spring-boot-starter/settings.gradle'.
Projects loaded. Root project using build file '/Users/leo/Documents/git/grpc-spring-boot-starter/build.gradle'.
Included projects: [root project 'org.lognet', project ':grpc-spring-boot-starter', project ':grpc-spring-boot-starter-demo']
Evaluating root project 'org.lognet' using build file '/Users/leo/Documents/git/grpc-spring-boot-starter/build.gradle'.
FAILURE: Build failed with an exception.
Where:
Build file '/Users/leo/Documents/git/grpc-spring-boot-starter/build.gradle' line: 45
What went wrong:
A problem occurred evaluating root project 'org.lognet'.
Could not find method compileOnly() for arguments [org.projectlombok:lombok:1.16.6] on org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler_Decorated@61ff6a49.
Try:
Run with --stacktrace option to get the stack trace. Run with --debug option to get more log output.
BUILD FAILED
Total time: 3.334 secs
Stopped 0 compiler daemon(s).
First of all, great job on the starter it is really light-weight and easy to use.
In your examples you have unary services only. I am using bidirectional services. Reading the gRPC documentation and recommendation they recommend using an InProcessServerBuilder
for testing. However, I could not find a way to do this with the current setup. What do you propose I should do for something like this?
In version 0.0.6, the org.lognet.springboot.grpc.GRpcServerRunner#run method tries to discover the @GRpcService
annotation. However, this fails if the bindableService
is wrapped as an AOP proxy.
A possible fix would be to check if the bindableService
is a Proxy, and use its target class instead to find the @GRpcService
annotation.
Motivation:
Simplify building of project
Change:
Add gradle 2.11 as wrapper and use it on travis.
Result:
We have control over the gradle version in use and make it easier to execute the build. We can upgrade to gradle 2.13 once we have upgraded the plugins as com.google.protobuf plugin currently in use is incompatible with gradle 2.13.
Greetings, thanks for the great contribution firstly, just wondering if possible to integrate with HealthStatusManager as part of the starter, and probably as well as consul(consul-template)/ or haproxy be part of the service discovery, governance utilities?
found ideas as this one: http://www.juhonkoti.net/2015/11/26/using-haproxy-to-do-health-checks-to-grpc-services
@GRpcService(grpcServiceOuterClass = HealthCheckServiceGrpc.class, serviceName = "HealthCheckGRpcService")
public class HealthCheckGRpcService extends HealthCheckServiceGrpc.HealthCheckServiceImplBase{
@Autowired
private HealthStatusManager healthStatusManager;
@Override
public void status(Health.StatusRequest request, StreamObserver<Health.HealthCheckResult> responseObserver) {
Health.HealthCheckResult.Builder builder = Health.HealthCheckResult.newBuilder();
builder.setStatus("1");
responseObserver.onNext(builder.build());
responseObserver.onCompleted();
}
@PostConstruct
public void healthInit(){
healthStatusManager.setStatus(this.getClass().getName(), HealthCheckResponse.ServingStatus.SERVING);
}
@PreDestroy
public void healthDestroy(){
healthStatusManager.clearStatus(this.getClass().getName());
}
}
Hi,
Thanks for this project. It's a great example. I'm trying to use it and the server configuration is too limited (e.g., no TLS). From the variety of examples in Google, it seems that it is not likely that all solutions could be covered using a general properties file. Therefore I would suggest using a bean for the ServerBuilder. Idea is that application would provide with the end point configured. You could then add to it the discovered services and their interceptors. The current code would be changed to locate the bean from the ApplicationContext. If the bean can't be retrieved, then the code would instantiate one using the current port configuration capability. What do you think?
Cheers, Pierre
While I understand and appreciate your personal preference for Gradle, there are plenty of developers out that that use Maven for various good reasons.
Each and every Spring-related project should also have a Maven pom.xml included in it.
Motivation:
Change:
Result:
In gRPC , how to add a global exception interceptor that intercepts any RuntimeException
and propagate meaningful information to the client ?
for example , a divide method may throw ArithmeticException
with / by zero message
. In the server side , I may write :
@Override
public void divide(DivideRequest request, StreamObserver<DivideResponse> responseObserver) {
int dom = request.getDenominator();
int num = request.getNumerator();
double result = num / dom;
responseObserver.onNext(DivideResponse.newBuilder().setValue(result).build());
responseObserver.onCompleted();
}
If the client passes denominator = 0 , it will get :
Exception in thread "main" io.grpc.StatusRuntimeException: UNKNOWN
And the server outputs
Exception while executing runnable io.grpc.internal.ServerImpl$JumpToApplicationThreadServerStreamListener$2@62e95ade
java.lang.ArithmeticException: / by zero
The client doesn't know what's going on.
If I want to pass / by zero
message to client , I have to modify server to :
try {
double result = num / dom;
responseObserver.onNext(DivideResponse.newBuilder().setValue(result).build());
responseObserver.onCompleted();
} catch (Exception e) {
logger.error("onError : {}" , e.getMessage());
responseObserver.onError(new StatusRuntimeException(Status.INTERNAL.withDescription(e.getMessage())));
}
And if client sends denominator = 0 , it will get :
Exception in thread "main" io.grpc.StatusRuntimeException: INTERNAL: / by zero
Good , / by zero
is passed to client.
But the problem is , in a truly enterprise environment, there will be a lot of RuntimeExceptions
, and if I want to pass these exception's messages to client , I will have to try catch each method , which is very cumbersome.
Is there any global interceptor that intercepts every method , catching RuntimeException
and trigger onError and propagate the error message to client ? So that I don't have to deal with RuntimeExceptions
in my server code .
I try to do like this :
@GRpcGlobalInterceptor
public class MyInterceptor implements ServerInterceptor {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call,
Metadata headers,
ServerCallHandler<ReqT, RespT> next) {
logger.info("intercept {}", call.getMethodDescriptor().getFullMethodName());
try {
return next.startCall(call, headers);
} catch (Throwable e) {
logger.error("Throwable : {}" , e.getMessage());
Status status = Status.INTERNAL.withDescription(e.getMessage());
// then ?
return ???
}
}
}
But it seems it cannot catch the Throwable
when I pass denominator = 0 .
I am stuck here ...
Any hints ?
Thanks
I use gradle to manage the jar files, but can't download the 2.1.2. And I check the jcenter repository, there is no 2.1.2 in it...
A quick note - gRPC is actually a recursive acronym. on the contrary it isn't Google RPC, it is actually gRPC Remote Procedure Call framework :)
Hi, thanks for your great starter.
It runs all OK except when I run integration test. With junit annotation like @RunWith(SpringRunner.class) @SpringBootTest
, it will create an grpc-spring-boot-starter instance together with application instance for each test case, meanwhile they all share the same port, which is 6565 by default. So I encounter this exceptions:
Failed to load ApplicationContext
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83)
at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:189)
at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:131)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:114)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:57)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:66)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:109)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:377)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.IllegalStateException: Failed to execute CommandLineRunner
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:803)
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:784)
at org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:771)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:111)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116)
... 45 more
Caused by: java.io.IOException: Failed to bind
at io.grpc.netty.NettyServer.start(NettyServer.java:158)
at io.grpc.internal.ServerImpl.start(ServerImpl.java:161)
at io.grpc.internal.ServerImpl.start(ServerImpl.java:83)
at org.lognet.springboot.grpc.GRpcServerRunner.run(GRpcServerRunner.java:67)
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:800)
... 51 more
Caused by: java.net.BindException: Address already in use: bind
at sun.nio.ch.Net.bind0(Native Method)
at sun.nio.ch.Net.bind(Net.java:433)
at sun.nio.ch.Net.bind(Net.java:425)
at sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:223)
at io.netty.channel.socket.nio.NioServerSocketChannel.doBind(NioServerSocketChannel.java:128)
at io.netty.channel.AbstractChannel$AbstractUnsafe.bind(AbstractChannel.java:554)
at io.netty.channel.DefaultChannelPipeline$HeadContext.bind(DefaultChannelPipeline.java:1258)
at io.netty.channel.AbstractChannelHandlerContext.invokeBind(AbstractChannelHandlerContext.java:502)
at io.netty.channel.AbstractChannelHandlerContext.bind(AbstractChannelHandlerContext.java:487)
at io.netty.channel.DefaultChannelPipeline.bind(DefaultChannelPipeline.java:980)
at io.netty.channel.AbstractChannel.bind(AbstractChannel.java:250)
at io.netty.bootstrap.AbstractBootstrap$2.run(AbstractBootstrap.java:365)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:403)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:445)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)
at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:144)
... 1 more
So, is there any advice to avoid this?
Hello,
I would like to be able to bind my gRPC on a dedicated interface.
It seems this is not possible:
GRpcServerBuilderConfigurer
gives access to an already created NettyServerBuilder
address
property cannot be changed.I am new to Spring and to this gRPC starter, so I'm wondering if maybe I have missed something obvious?
The current workaround would be to let the app bind on 0.0.0.0
and rely on a properly configured firewall. I would rather have the app bind to the expected interface.
Thanks,
You're doing this for free of course, so I'm definitely not complaining, but it would be pretty fantastic if we could count on this repository to be updated more quickly.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.