Giter Site home page Giter Site logo

lognet / grpc-spring-boot-starter Goto Github PK

View Code? Open in Web Editor NEW
2.2K 85.0 428.0 16.73 MB

Spring Boot starter module for gRPC framework.

License: Apache License 2.0

Java 99.88% Shell 0.08% Kotlin 0.05%
grpc grpc-service spring-boot spring-boot-starter spring spring-cloud grpc-framework java

grpc-spring-boot-starter's Introduction

Spring boot starter for gRPC framework.

grpc spring boot starter Build Status Codecov Stand With Ukraine

1. Features

Autoconfigures and runs the embedded gRPC server with @GRpcService-enabled beans as part of spring-boot application (short video)

2. Setup

2.1. Long story short

Gradle users are advised to apply the plugin :

plugins {
id "io.github.lognet.grpc-spring-boot" version '5.1.5'
}

io.github.lognet.grpc-spring-boot gradle plugin dramatically simplifies the project setup.

2.2. Short story long

repositories {
  mavenCentral()
  // maven { url "https://oss.sonatype.org/content/repositories/snapshots" } // for snapshot builds
}
dependencies {
  implementation 'io.github.lognet:grpc-spring-boot-starter:5.1.5'
}

By default, starter pulls io.grpc:grpc-netty-shaded as transitive dependency, if you are forced to use pure grpc-netty dependency:

implementation ('io.github.lognet:grpc-spring-boot-starter:5.1.5') {
  exclude group: 'io.grpc', module: 'grpc-netty-shaded'
}
implementation 'io.grpc:grpc-netty:1.58.0' // (1)
  1. Make sure to pull the version that matches the release.

Both libraries' presence on classpath is also supported with grpc.netty-server.on-collision-prefer-shaded-netty property.

If you are using Spring Boot Dependency Management plugin, it might pull not the same version as the version this started was compiled against, causing binary incompatibility issue.
In this case you’ll need to forcibly and explicitly set the grpc version to use (see version matrix here ):

configurations.all {
  resolutionStrategy.eachDependency { details ->
    if ("io.grpc".equalsIgnoreCase(details.requested.group)) {
      details.useVersion "1.58.0"
    }
  }
}
ℹ️
The release notes with compatibility matrix can be found here

Follow this guide to generate stub and server interface(s) from your .proto file(s).

If you are stack with maven - use this link.

3. Usage

  • Annotate your server interface implementation(s) with @org.lognet.springboot.grpc.GRpcService

  • Optionally configure the server port in your application.yml/properties. Default port is 6565.

 grpc:
    port: 6565
ℹ️
A random port can be defined by setting the port to 0.
The actual port being used can then be retrieved by using @LocalRunningGrpcPort annotation on int field which will inject the running port (explicitly configured or randomly selected)
 grpc:
    enableReflection: true
 grpc:
    start-up-phase: XXX
  • Optionally set the number of seconds to wait for preexisting calls to finish during graceful server shutdown. New calls will be rejected during this time. A negative value is equivalent to an infinite grace period. Default value is 0 (means don’t wait).

 grpc:
    shutdownGrace: 30
  • Netty-specific server properties can be specified under grpc.netty-server prefix.
    By configuring one of the grpc.netty-server.xxxx values you are implicitly setting transport to be Netty-based.

grpc:
  netty-server:
    keep-alive-time: 30s (1)
    max-inbound-message-size: 10MB (2)
    primary-listen-address: 10.10.15.23:0 (3)
    additional-listen-addresses:
      - 192.168.0.100:6767 (4)
    on-collision-prefer-shaded-netty: false (5)
  1. Duration type properties can be configured with string value format described here.

  2. DataSize type properties can be configured with string value described here

  3. Exposed on external network IP with custom port.
    SocketAddress type properties string value format:

    • host:port (if port value is less than 1, uses random value)

    • host: (uses default grpc port, 6565 )

  4. Exposed on internal network IP as well with predefined port 6767.

  5. In case you have both shaded and pure netty libraries in dependencies, pick the NettyServerBuilder type that should be created. This is the type that will be passed to GRpcServerBuilderConfigurer (see Custom gRPC Server Configuration), defaults to true(i.e. io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder; io.grpc.netty.NettyServerBuilder if false)

The starter supports also the in-process server, which should be used for testing purposes :

 grpc:
    enabled: false (1)
    inProcessServerName: myTestServer (2)
  1. Disables the default server (NettyServer).

  2. Enables the in-process server.

ℹ️
If you enable both the NettyServer and in-process server, they will both share the same instance of HealthStatusManager and GRpcServerBuilderConfigurer (see Custom gRPC Server Configuration).

4. Showcase

In the grpc-spring-boot-starter-demo project you can find fully functional examples with integration tests.

4.1. Service implementation

The service definition from .proto file looks like this :

service Greeter {
    rpc SayHello ( HelloRequest) returns (  HelloReply) {}
}

Note the generated io.grpc.examples.GreeterGrpc.GreeterImplBase class that extends io.grpc.BindableService.

All you need to do is to annotate your service implementation with @org.lognet.springboot.grpc.GRpcService

    @GRpcService
    public static class GreeterService extends  GreeterGrpc.GreeterImplBase{
        @Override
        public void sayHello(GreeterOuterClass.HelloRequest request, StreamObserver<GreeterOuterClass.HelloReply> responseObserver) {
            final GreeterOuterClass.HelloReply.Builder replyBuilder = GreeterOuterClass.HelloReply.newBuilder().setMessage("Hello " + request.getName());
            responseObserver.onNext(replyBuilder.build());
            responseObserver.onCompleted();
        }
    }

4.2. Interceptors support

The starter supports the registration of two kinds of interceptors: Global and Per Service.
In both cases the interceptor has to implement io.grpc.ServerInterceptor interface.

  • Per service

@GRpcService(interceptors = { LogInterceptor.class })
public  class GreeterService extends  GreeterGrpc.GreeterImplBase{
    // ommited
}

LogInterceptor will be instantiated via spring factory if there is bean of type LogInterceptor, or via no-args constructor otherwise.

  • Global

@GRpcGlobalInterceptor
public  class MyInterceptor implements ServerInterceptor{
    // ommited
}

The annotation on java config factory method is also supported :

 @Configuration
 public class MyConfig{
     @Bean
     @GRpcGlobalInterceptor
     public  ServerInterceptor globalInterceptor(){
         return new ServerInterceptor(){
             @Override
             public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
                // your logic here
                 return next.startCall(call, headers);
             }
         };
     }
 }

The particular service also has the opportunity to disable the global interceptors :

@GRpcService(applyGlobalInterceptors = false)
public  class GreeterService extends  GreeterGrpc.GreeterImplBase{
    // ommited
}

4.2.1. Interceptors ordering

Global interceptors can be ordered using Spring’s @Ordered or @Priority annotations. Following Spring’s ordering semantics, lower order values have higher priority and will be executed first in the interceptor chain.

@GRpcGlobalInterceptor
@Order(10)
public  class A implements ServerInterceptor{
    // will be called before B
}

@GRpcGlobalInterceptor
@Order(20)
public  class B implements ServerInterceptor{
    // will be called after A
}

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

  • grpc.recovery.interceptor-order (error handling interceptor order, defaults to Ordered.HIGHEST_PRECEDENCE)

  • grpc.security.auth.interceptor-order ( defaults to Ordered.HIGHEST_PRECEDENCE+1)

  • grpc.validation.interceptor-order ( defaults to Ordered.HIGHEST_PRECEDENCE+10)

  • grpc.metrics.interceptor-order ( defaults to Ordered.HIGHEST_PRECEDENCE+20)

This gives you the ability to set up the desired order of built-in and your custom interceptors.

Keep on reading !!! There is more

The way grpc interceptor works is that it intercepts the call and returns the server call listener, which in turn can intercept the request message as well, before forwarding it to the actual service call handler :

interceptors 001

By setting grpc.security.auth.fail-fast property to false all downstream interceptors as well as all upstream interceptors (On_Message) will still be executed in case of authentication/authorization failure

Assuming interceptor_2 is securityInterceptor :

  • For failed authentication/authorization with grpc.security.auth.fail-fast=true(default):

    interceptors 002

  • For failed authentication/authorization with grpc.security.auth.fail-fast=false:

    interceptors 003

4.3. Distributed tracing support (Spring Cloud Sleuth integration)

This started is natively supported by spring-cloud-sleuth project.
Please continue to sleuth grpc integration.

4.4. GRPC server metrics (Micrometer.io integration)

By including org.springframework.boot:spring-boot-starter-actuator dependency, the starter will collect gRPC server metrics , broken down by

  1. method - gRPC service method FQN (Fully Qualified Name)

  2. result - Response status code

  3. address - server local address (if you exposed additional listen addresses, with grpc.netty-server.additional-listen-addresses property)

After configuring the exporter of your choice, you should see the timer named grpc.server.calls.

4.4.1. Custom tags support

By defining GRpcMetricsTagsContributor bean in your application context, you can add custom tags to the grpc.server.calls timer.
You can also use RequestAwareGRpcMetricsTagsContributor bean to tag unary and streaming calls.
Demo is here

💡
Keep the dispersion low not to blow up the cardinality of the metric.

RequestAwareGRpcMetricsTagsContributor can be still executed for failed authentication if metric interceptor has higher precedence than security interceptor and grpc.security.auth.fail-fast set to false.
This case is covered by this test.

💡
Make sure to read Interceptors ordering chapter.

4.4.2. Exposing Prometheus endpoint

Make sure to include below dependencies :

implementation "org.springframework.boot:spring-boot-starter-actuator"
implementation "io.micrometer:micrometer-registry-prometheus"
implementation 'org.springframework.boot:spring-boot-starter-web'

Configuration :

management:
  metrics:
    export:
      prometheus:
        enabled: true
  endpoints:
    web:
      exposure:
        include: "*"

Standard /actuator/metrics and /actuator/prometheus endpoints will render grpc.server.calls metrics (see demo here).

ℹ️
GRPC scrapping proposal

4.5. Spring Boot Validation support

The starter can be autoconfigured to validate request/response gRPC service messages. Please continue to Implementing message validation for configuration details.

4.6. Spring cloud stream support

The starter internally defines the bean of type java.util.function.Consumer which is being considered for function registry when spring-cloud-stream is on classpath, which is undesirable (spring-cloud-stream auto-registers the channel if you have exactly one Consumer/Supplier/Function bean in the application context, so you already have one if you use this starter together with spring-cloud-stream).

According to this, it is recommended to use spring.cloud.function.definition property in production ready applications and not to rely on the auto-discovery.

Please refer to GRPC Kafka Stream demo, the essential part is this line.

4.7. Spring security support

The starter provides built-in support for authenticating and authorizing users leveraging integration with Spring Security framework.
Please refer to the sections on Spring Security Integration for details on supported authentication providers and configuration options.

4.8. Transport Security (TLS)

The transport security can be configured using root certificate together with its private key path:

 grpc:
    security:
      cert-chain: classpath:cert/server-cert.pem
      private-key: file:../grpc-spring-boot-starter-demo/src/test/resources/cert/server-key.pem

The value of both properties is in form supported by ResourceEditor.

The client side should be configured accordingly :

((NettyChannelBuilder)channelBuilder)
 .useTransportSecurity()
 .sslContext(GrpcSslContexts.forClient().trustManager(certChain).build());

This starter will pull the io.netty:netty-tcnative-boringssl-static dependency by default to support SSL.
If you need another SSL/TLS support, please exclude this dependency and follow Security Guide.

ℹ️
If the more detailed tuning is needed for security setup, please use custom configurer described in Custom gRPC Server Configuration

4.9. Custom gRPC Server Configuration

To intercept the io.grpc.ServerBuilder instance used to build the io.grpc.Server, you can add bean that inherits from org.lognet.springboot.grpc.GRpcServerBuilderConfigurer to your context and override the configure method.
Multiple configurers are also supported.
By the time of invocation of configure method, all discovered services, including theirs interceptors, had been added to the passed builder.
In your implementation of configure method, you can add your custom configuration:

@Component
public class MyGRpcServerBuilderConfigurer extends GRpcServerBuilderConfigurer{
        @Override
        public void configure(ServerBuilder<?> serverBuilder){
            serverBuilder
                .executor(YOUR EXECUTOR INSTANCE)
                .useTransportSecurity(YOUR TRANSPORT SECURITY SETTINGS);
            ((NettyServerBuilder)serverBuilder)// cast to NettyServerBuilder (which is the default server) for further customization
                    .sslContext(GrpcSslContexts  // security fine tuning
                                    .forServer(...)
                                    .trustManager(...)
                                    .build())
                    .maxConnectionAge(...)
                    .maxConnectionAgeGrace(...);

        }
    };
}
@Component
public class MyCustomCompressionGRpcServerBuilderConfigurer extends GRpcServerBuilderConfigurer{
        @Override
        public void configure(ServerBuilder<?> serverBuilder){
            serverBuilder
                .compressorRegistry(YOUR COMPRESSION REGISTRY)
                .decompressorRegistry(YOUR DECOMPRESSION REGISTRY) ;

        }
    };
}
ℹ️
If you enable both NettyServer and in-process servers, the configure method will be invoked on the same instance of configurer.
If you need to differentiate between the passed serverBuilder s, you can check the type.
This is the current limitation.

5. Events

GRpcServerInitializedEvent is published upon server startup, you can consume it using regular spring API.

6. Reactive API support

Starting from version 5.1.0, spring-boot-starter-gradle-plugin integrates SalesForce’s reactive-grpc protoc plugin :

import org.lognet.springboot.grpc.gradle.ReactiveFeature
plugins {
  id "io.github.lognet.grpc-spring-boot"
}
grpcSpringBoot {
  reactiveFeature.set(ReactiveFeature.REACTOR) // or ReactiveFeature.RX
}

7. Error handling

The starter registers the GRpcExceptionHandlerInterceptor which is responsible to propagate the service-thrown exception to the error handlers.
The error handling method could be registered by having @GRpcServiceAdvice annotated bean with methods annotated with @GRpcExceptionHandler annotations.
These are considered as global error handlers and the method with exception type parameter the nearest by the type hierarchy to the thrown exception is invoked.
The signature of the error handler has to follow the below pattern:

Return type Parameter 1 Parameter 2

io.grpc.Status

any Exception type

GRpcExceptionScope

Sample
@GRpcServiceAdvice
class MyHandler1{
    @GRpcExceptionHandler
    public Status handle (MyCustomExcpetion exc, GRpcExceptionScope scope){

    }
    @GRpcExceptionHandler
    public Status handle (IllegalArgumentException exc, GRpcExceptionScope scope){

    }

}
@GRpcServiceAdvice
class MyHandler2 {
    @GRpcExceptionHandler
   public Status anotherHandler (NullPointerException npe,GRpcExceptionScope scope){

   }
}

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.

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:

Sample
class SomeException extends Exception{

}
class SomeRuntimeException extends RuntimeException{

}

@GRpcService
public  class HelloService extends GreeterGrpc.GreeterImplBase{
    @Override
    public void sayHello(GreeterOuterClass.HelloRequest request, StreamObserver<GreeterOuterClass.HelloReply> responseObserver) {
        ...
    throw new GRpcRuntimeExceptionWrapper(new SomeException()) ; // (1)
//or
    throw new GRpcRuntimeExceptionWrapper(new SomeException(), "myHint");// (2)
//or
    throw new SomeRuntimeException(); //(3)
    }
   @GRpcExceptionHandler
   public Status privateHandler (SomeException npe,GRpcExceptionScope scope){
        // INVOKED when thrown from  HelloService service
        String myHint = scope.getHintAs(String.class);   // (4)
        scope.getResponseHeaders().put(Metadata.Key.of("custom", Metadata.ASCII_STRING_MARSHALLER), "Value");// (5)
   }
   @GRpcExceptionHandler
   public Status privateHandler (SomeRuntimeException npe,GRpcExceptionScope scope){
        // INVOKED when thrown from  HelloService service

   }
}
@GRpcServiceAdvice
class MyHandler  {
   @GRpcExceptionHandler
   public Status anotherHandler (SomeException npe,GRpcExceptionScope scope){
        // NOT INVOKED when thrown from  HelloService service
   }
   @GRpcExceptionHandler
   public Status anotherHandler (SomeRuntimeException npe,GRpcExceptionScope scope){
        // NOT INVOKED when thrown from  HelloService service
   }

}
  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.

  2. When throwing the GRpcRuntimeExceptionWrapper exception, you can also pass the hint object which is then accessible from the scope object in handler method.

  3. Runtime exception can be thrown as-is and doesn’t need to be wrapped.

  4. Obtain the hint object.

  5. Send custom headers to the client.

Authentication failure is propagated via AuthenticationException and authorization failure - via AccessDeniedException.

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.

The demo is here

8. Implementing message validation

Thanks to Bean Validation configuration support via XML deployment descriptor , it’s possible to provide the constraints for generated classes via XML instead of instrumenting the generated messages with custom protoc compiler.

  1. Add org.springframework.boot:spring-boot-starter-validation dependency to your project.

  2. Create META-INF/validation.xml and constraints declarations file(s). (IntelliJ IDEA has great auto-complete support for authorizing bean validation constraints xml files )
    See also samples from Hibernate validator documentation

You can find demo configuration and corresponding tests here

Note, that both request and response messages are being validated.

If your gRPC method uses the same request and response message type, you can use org.lognet.springboot.grpc.validation.group.RequestMessage and org.lognet.springboot.grpc.validation.group.ResponseMessage validation groups to apply different validation logic :

...
<getter name="someField">
	<!--should be empty for request message-->
	<constraint annotation="javax.validation.constraints.Size">
		<groups>
			<value>org.lognet.springboot.grpc.validation.group.RequestMessage</value> (1)
		</groups>
		<element name="min">0</element>
		<element name="max">0</element>
	</constraint>
	<!--should NOT  be empty for response message-->
	<constraint annotation="javax.validation.constraints.NotEmpty">
		<groups>
			<value>org.lognet.springboot.grpc.validation.group.ResponseMessage</value> (2)
		</groups>
	</constraint>
</getter>
...
  1. Apply this constraint only for request message

  2. Apply this constraint only for response message

Note also custom cross-field constraint and its usage :

<bean class="io.grpc.examples.GreeterOuterClass$Person">
	<class>
		<constraint annotation="org.lognet.springboot.grpc.demo.PersonConstraint"/>
	</class>
    <!-- ... -->
</bean>

As described in Interceptors ordering chapter, you can give validation interceptor the higher precedence than security interceptor and set grpc.security.auth.fail-fast property to false.
In this scenario, if call is both unauthenticated and invalid, the client will get Status.INVALID_ARGUMENT instead of Status.PERMISSION_DENIED/Status.UNAUTHENTICATED response status. Demo is here

9. GRPC response observer and Spring @Transactional caveats

While it’s still possible to have your rpc methods annotated with @Transactional (with spring.aop.proxy-target-class=true if it’s not enabled by default), chances are to get unpredictable behaviour. Consider below grpc method implementation :

@GRpcService
class MyGrpcService extends ...{
    @Autowired
    private MyJpaRepository repo;

    @Transactional //(1)
    public void rpcCall(Req request, StreamOvserver<Res> observer) {
        Res response = // Database operations via repo
        observer.onNext(response); //(2)
        observer.onCompleted();
    }//(3)
}
  1. The method is annotated as @Transactional, Spring will commit the transaction at some time after methods returns

  2. Response is returned to the caller

  3. Methods returns, transaction eventually committed.

Theoretically, and as you can see - practically, there is small time-span when client (if the network latency is minimal, and your grpc server encouraged context switch right after <2>) can try to access the database via another grpc call before the transaction is committed.

The solution to overcome this situation is to externalize the transactional logic into separate service class :

@Service
class MyService{
    @Autowired
    private MyJpaRepository repo;

    @Transactional //(1)
    public Res doTransactionalWork(){
        // Database operations via repo
        return result;
    }//(2)
}
@GRpcService
class MyGrpcService extends ...{
    @Autowired
    private MyService myService;

    public void rpcCall(Req request, StreamOvserver<Res> observer) {
        Res response = myService.doTransactionalWork();
        observer.onNext(response); //(3)
        observer.onCompleted();
    }
}
  1. Service method is transactional

  2. Transaction is eventually committed.

  3. Reply after transaction is committed.

By following this approach you also decouple the transport layer and business logic that now can be tested separately.

10. Spring Security Integration

10.1. Setup

Table 1. Dependencies to implement authentiction scheme (to be added to server-side project)
Scheme Dependencies

Basic

  • org.springframework.security:spring-security-config

Bearer

  • org.springframework.security:spring-security-config

  • org.springframework.security:spring-security-oauth2-jose

  • org.springframework.security:spring-security-oauth2-resource-server

Custom

  • org.springframework.security:spring-security-config

  • your.custom.lib

10.2. Server side configuration

GRPC security configuration follows the same principals and APIs as Spring WEB security configuration, it’s enabled by default if you have org.springframework.security:spring-security-config dependency in your classpath.

You can use @Secured annotation on services/methods to protect your endpoints, or by using API and overriding defaults (which precesses @Secured annotation ):

 @Configuration
   class MySecurityCfg extends GrpcSecurityConfigurerAdapter {
        @Override
        public void configure(GrpcSecurity builder) throws Exception {
            MethodsDescriptor<?,?> adminMethods = MyServiceGrpc.getSomeMethod();
            builder
                    .authorizeRequests()
                    .methods(adminMethods).hasAnyRole("admin")
                    .anyMethodExcluding(adminMethods).hasAnyRole("user")
                    .withSecuredAnnotation();(1)
        }
    }
  1. or combine API with @Secured annotations.

10.2.1. Default

This default configuration secures GRPC methods/services annotated with org.springframework.security.access.annotation.@Secured annotation.
Leaving value of the annotation empty (@Secured({})) means : authenticate only, no authorization will be performed.

If JwtDecoder bean exists in your context, it will also register JwtAuthenticationProvider to handle the validation of authentication claim.

BasicAuthSchemeSelector and BearerTokenAuthSchemeSelector are also automatically registered to support authentication with username/password and bearer token.

By setting grpc.security.auth.enabled to false, GRPC security can be turned-off.

10.2.2. Custom

Customization of GRPC security configuration is done by extending GrpcSecurityConfigurerAdapter (Various configuration examples and test scenarios are here.)

    @Configuration
    public class GrpcSecurityConfiguration extends GrpcSecurityConfigurerAdapter {
        @Autowired
        private JwtDecoder jwtDecoder;

        @Override
        public void configure(GrpcSecurity builder) throws Exception {

            builder.authorizeRequests()(1)
                    .methods(GreeterGrpc.getSayHelloMethod()).hasAnyAuthority("SCOPE_profile")(2)
            .and()
                    .authenticationProvider(JwtAuthProviderFactory.withAuthorities(jwtDecoder));(3)
        }
    }
  1. Get hold of authorization configuration object

  2. MethodDefinition of sayHello method is allowed for authenticated users with SCOPE_profile authority.

  3. Use JwtAuthenticationProvider to validate user claim (BEARER token) against resource server configured with spring.security.oauth2.resourceserver.jwt.issuer-uri property.

10.2.3. DIY

One is possible to plug in your own bespoke authentication provider by implementing AuthenticationSchemeSelector interface.

@Configuration
    public class GrpcSecurityConfiguration extends GrpcSecurityConfigurerAdapter {
    @Override
        public void configure(GrpcSecurity builder) throws Exception {
        builder.authorizeRequests()
                    .anyMethod().authenticated()//(1)
                    .and()
                    .authenticationSchemeSelector(new AuthenticationSchemeSelector() { //(2)
                            @Override
                            public Optional<Authentication> getAuthScheme(CharSequence authorization) {
                                return new MyAuthenticationObject();// (3)
                            }
                        })
                    .authenticationProvider(new AuthenticationProvider() {// (4)
                        @Override
                        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                            MyAuthenticationObject myAuth= (MyAuthenticationObject)authentication;
                            //validate myAuth
                            return MyValidatedAuthenticationObject(withAuthorities);//(5)
                        }

                        @Override
                        public boolean supports(Class<?> authentication) {
                            return MyAuthenticationObject.class.isInstance(authentication);
                        }
                    });
 }
 }
  1. Secure all services methods.

  2. Register your own AuthenticationSchemeSelector.

  3. Based on provided authorization header - return Authentication object as a claim (not authenticated yet)

  4. Register your own AuthenticationProvider that supports validation of MyAuthenticationObject

  5. Validate provided authentication and return validated and authenticated Authentication object

AuthenticationSchemeSelector can also be registered by defining Spring bean in your application context:

@Bean
public AuthenticationSchemeSelector myCustomSchemeSelector(){
     return authHeader->{
         // your logic here
     };
}

Client side configuration support section explains how to pass custom authorization scheme and claim from GRPC client.

10.3. @PreAuthorize() and @PostAuthorize() support

Starting from version 4.5.9 you can also use standard @PreAuthorize and @PostAuthorize annotations on grpc service methods and grpc service types.

Table 2. Referencing input/output object in expression
Call Type Input object ref Output object ref Sample

Unary
(request-response)

By parameter name

returnObject

@Override
@PreAuthorize("#person.age<12")
@PostAuthorize("returnObject.description.length()>0")
public void unary(Person person, StreamObserver<Assignment> responseObserver) {
    }

Input stream,
single response

#p0 or #a0

returnObject

@Override
@PreAuthorize("#p0.getAge()<12")
@PostAuthorize("returnObject.description.length()>0")
public StreamObserver<Person> inStream(StreamObserver<Assignment> responseObserver) {
    }

Single request,
output stream

By parameter name

returnObject

@Override
@PreAuthorize("#person.age<12")
@PostAuthorize("returnObject.description.length()>0")
public void outStream(Person person, StreamObserver<Assignment> responseObserver) {
}

Bidi stream

#p0 or #a0

returnObject

@Override
@PreAuthorize("#p0.age<12")
@PostAuthorize("returnObject.description.length()>0")
public StreamObserver<Person> bidiStream(StreamObserver<Assignment> responseObserver) {
}

10.4. Obtaining Authentication details

To obtain Authentication object in the implementation of secured method, please use below snippet

final Authentication auth = GrpcSecurity.AUTHENTICATION_CONTEXT_KEY.get();

Starting from 4.5.6, the Authentication object can also be obtained via standard Spring API :

final Authentication auth = SecurityContextHolder.getContext().getAuthentication();

10.5. Client side configuration support

By adding io.github.lognet:grpc-client-spring-boot-starter dependency to your java grpc client application you can easily configure per-channel or per-call credentials :

Per-channel
class MyClient{
    public void doWork(){
        final AuthClientInterceptor clientInterceptor = new AuthClientInterceptor((1)
                AuthHeader.builder()
                    .bearer()
                    .binaryFormat(true)(3)
                    .tokenSupplier(this::generateToken)(4)
        );

        Channel authenticatedChannel = ClientInterceptors.intercept(
                ManagedChannelBuilder.forAddress("host", 6565).build(), clientInterceptor (2)
        );
        // use authenticatedChannel to invoke GRPC service
    }

     private ByteBuffer generateToken(){ (4)
         // generate bearer token against your resource server
     }
 }
  1. Create client interceptor

  2. Intercept channel

  3. Turn the binary format on/off:

    • When true, the authentication header is sent with Authorization-bin key using binary marshaller.

    • When false, the authentication header is sent with Authorization key using ASCII marshaller.

  4. Provide token generator function (Please refer to for example.)

Per-call
class MyClient{
    public void doWork(){
        AuthCallCredentials callCredentials = new AuthCallCredentials( (1)
                AuthHeader.builder().basic("user","pwd".getBytes())
        );

        final SecuredGreeterGrpc.SecuredGreeterBlockingStub securedFutureStub = SecuredGreeterGrpc.newBlockingStub(ManagedChannelBuilder.forAddress("host", 6565));(2)

        final String reply = securedFutureStub
                .withCallCredentials(callCredentials)(3)
                .sayAuthHello(Empty.getDefaultInstance()).getMessage();

    }
 }
  1. Create call credentials with basic scheme

  2. Create service stub

  3. Attach call credentials to the call

    AuthHeader could also be built with bespoke authorization scheme :

     AuthHeader
       .builder()
       .authScheme("myCustomAuthScheme")
       .tokenSupplier(()->generateMyCustomToken())

11. Health check

The starter registers the default implementation of HealthServiceImpl.
You can provide you own by registering ManagedHealthStatusService bean in your application context.

12. Spring actuator support

If you have org.springframework.boot:spring-boot-starter-actuator and org.springframework.boot:spring-boot-starter-web in the classpath, the starter will expose:

  • grpc health indicator under /actuator/health endpoint.

  • /actuator/grpc endpoint.

This can be controlled by standard endpoints and health configuration.

13. Consul Integration

Starting from version 3.3.0, the starter will auto-register the running grpc server in Consul registry if org.springframework.cloud:spring-cloud-starter-consul-discovery is in classpath and spring.cloud.service-registry.auto-registration.enabled is NOT set to false.

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.

ConsulDiscoveryProperties are bound from configuration properties prefixed by spring.cloud.consul.discovery and then the values are overwritten by grpc.consul.discovery prefixed properties (if set). This allows you to have separate consul discovery configuration for rest and grpc services if you choose to expose both from your application.

spring:
  cloud:
    consul:
      discovery:
        metadata:
          myKey: myValue (1)
        tags:
          - myWebTag (2)
grpc:
  consul:
    discovery:
      tags:
        - myGrpcTag (3)
  1. Both rest and grpc services are registered with metadata myKey=myValue

  2. Rest services are registered with myWebTag

  3. Grpc services are registered with myGrpcTag

Setting spring.cloud.consul.discovery.register-health-check (or grpc.consul.discovery.register-health-check) to true will register GRPC health check service with Consul.

There are 4 supported registration modes :

  1. SINGLE_SERVER_WITH_GLOBAL_CHECK (default)
    In this mode the running grpc server is registered as single service with single grpc check with empty serviceId.
    Please note that default implementation does nothing and simply returns SERVING status. You might want to provide your custom Health check implementation for this mode.

  2. SINGLE_SERVER_WITH_CHECK_PER_SERVICE
    In this mode the running grpc server is registered as single service with check per each discovered grpc service.

  3. STANDALONE_SERVICES
    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.

  4. NOOP - no grpc services registered. This mode is useful if you serve both rest and grpc services in your application, but for some reason, only rest services should be registered with Consul.

You can control the desired mode from application.properties
grpc:
  consule:
    registration-mode: SINGLE_SERVER_WITH_CHECK_PER_SERVICE

14. Eureka Integration

When building production-ready services, the advise is to have separate project for your service(s) gRPC API that holds only proto-generated classes both for server and client side usage.
You will then add this project as implementation dependency to your gRPC client and gRPC server projects.

To integrate Eureka simply follow the great guide from Spring.

Below are the essential parts of configurations for both server and client projects.

14.1. gRPC Server Project

  • Add eureka starter as dependency of your server project together with generated classes from proto files:

build.gradle
 dependencies {
     implementation('org.springframework.cloud:spring-cloud-starter-eureka')
     implementation project(":yourProject-api")
 }
  • Configure gRPC server to register itself with Eureka.

    bootstrap.yaml
    spring:
        application:
            name: my-service-name (1)
    1. Eureka’s ServiceId by default is the spring application name, provide it before the service registers itself with Eureka.

      application.yaml
      grpc:
          port: 6565 (1)
      eureka:
          instance:
              nonSecurePort: ${grpc.port} (2)
          client:
              serviceUrl:
                  defaultZone: http://${eureka.host:localhost}:${eureka.port:8761}/eureka/ (3)
    2. Specify the port number the gRPC is listening on.

    3. Register the eureka service port to be the same as grpc.port so client will know where to send the requests to.

    4. Specify the registry URL, so the service will register itself with.

  • Expose the gRPC service as part of Spring Boot Application.

    EurekaGrpcServiceApp.java
     @SpringBootApplication
     @EnableEurekaClient
     public class EurekaGrpcServiceApp {
    
         @GRpcService
         public static class GreeterService extends GreeterGrpc.GreeterImplBase {
             @Override
             public void sayHello(GreeterOuterClass.HelloRequest request, StreamObserver<GreeterOuterClass.HelloReply> responseObserver) {
    
             }
         }
    
         public static void main(String[] args) {
             SpringApplication.run(DemoApp.class,args);
         }
     }

14.2. gRPC Client Project

  • Add eureka starter as dependency of your client project together with generated classes from proto files:

build.gradle
 dependencies {
     implementation('org.springframework.cloud:spring-cloud-starter-eureka')
     implementation project(":yourProject-api")
 }
  • Configure client to find the eureka service registry:

application.yaml
eureka:
  client:
    register-with-eureka: false (1)
    service-url:
      defaultZone: http://${eureka.host:localhost}:${eureka.port:8761}/eureka/ (2)
  1. false if this project is not meant to act as a service to another client.

  2. Specify the registry URL, so this client will know where to look up the required service.

GreeterServiceConsumerApplication.java
@EnableEurekaClient
@SpringBootApplication
public class GreeterServiceConsumerApplication {
 public static void main(String[] args) {
   SpringApplication.run(GreeterServiceConsumerApplication.class, args);
 }
}
  • Use EurekaClient to get the coordinates of gRPC service instance from Eureka and consume the service :

GreeterServiceConsumer.java
@EnableEurekaClient
@Component
public class GreeterServiceConsumer {
    @Autowired
    private EurekaClient client;

    public void greet(String name) {
        final InstanceInfo instanceInfo = client.getNextServerFromEureka("my-service-name", false);//(1)
        final ManagedChannel channel = ManagedChannelBuilder.forAddress(instanceInfo.getIPAddr(), instanceInfo.getPort())
                .usePlaintext()
                .build(); //(2)
        final GreeterServiceGrpc.GreeterServiceFutureStub stub = GreeterServiceGrpc.newFutureStub(channel); //(3)
        stub.greet(name); //(4)

    }
}
  1. Get the information about the my-service-name instance.

  2. Build channel accordingly.

  3. Create stub using the channel.

  4. Invoke the service.

15. License

Apache 2.0

grpc-spring-boot-starter's People

Contributors

a-gaitian avatar aaabramov avatar afurer avatar aglophotis avatar cleverunderdog avatar dadadom avatar dounan avatar imyxiao avatar jgleitz avatar joholland avatar jorgheymans avatar jvmlet avatar mchernyakov avatar nsobadzhiev avatar pink-lucifer avatar robertoduessmann avatar sarrruman avatar shina-blueprint avatar sineaggi avatar skjolber avatar tsonglew avatar willtaylor avatar yu-shiba avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

grpc-spring-boot-starter's Issues

Using Spring 5 webflux with GRPC uses 2 different ports

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.

Allow random port selection

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.

Update Gradle to version 4+

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.

Accessing Metadata in @GRpcService

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.

@GRpcGlobalInterceptor beans, created by @Bean inside @Configuration, are ignored if there is a class with annotation @GRpcGlobalInterceptor

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.

gradle build exception

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).

Please update gRPC

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.

Allow to use Reactor Flux or RxJava Observable instead of gRPC StreamObserver

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 ...

Wildcard imports in Java are bad practice

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

Testing non-blocking bidirectional transfer

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?

Update to proto 3.0.0

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

Use "compileOnly" configuration from gradle 2.13

Motivation:

  • gradle 2.13 introduced "compileOnly" for compiletime-only dependencies.

Change:

  • remove springframework-propdeps plugin
  • use compileOnly for "provided" and "optional" dependencies

Result:

  • One less gradle plugin needed / more standard features in use

grpc services and @Transactional

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)

build polishing

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)

  • add gradle wrapper
  • use "compileOnly" configuration from gradle 2.13
  • upgrade dependencies (spring-boot, grpc, spring-cloud, lombok)
  • missing .gitignore
  • do you really want to check-in the generated code (grpc-spring-boot-starter-demo/src/protoGen/)

Add gradle wrapper

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.

NPE thrown when trying to discover annotations on AOP Proxy objects

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.

Configurable server

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

HealthCheck

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());
}

}

Configure bind address

Hello,

I would like to be able to bind my gRPC on a dedicated interface.
It seems this is not possible:

  • This starter's configuration exposes only a port not an address.
  • Extending GRpcServerBuilderConfigurer gives access to an already created NettyServerBuilder
    and therefore the 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,

No Support for Maven builds

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.

Facing issue while running GRPC in embedded mode

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]

@Bean interceptor not taken into account

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 ?

not found variable log?

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 ...");

Joining effort, integrate into 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:

  • No Java 8 dependence
  • Configurable discovery mechanism
  • Focused on the server side, we can have a separate grpc-client starter
  • Preconfigured Maven plugin

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!

README Google RPC -> gRPC

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 :)

can not access context's variables

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

Integration test encounter port already in binding error.

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?

Support Spring Security @Secured annotation

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

better shutdown ?

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();

Add autoconfiguration support for distributed tracing

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)
  • Write a custom Conditional that evaluates to true if you have at least one bean with the @GRpcService.

For client-side interceptor:

  • @ConditionalOnClass(GrpcTracing.class)
  • Put the client-side interceptor into the context.
  • An interesting idea would be to also put a ManagedChannelBuilder into the context @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.

Upgrade protobuf-gradle-plugin

Motivation:

  • com.google.protobuf plugin is incompatible with latest gradle
  • latest com.google.protobuf requires gradle 2.12+

Change:

  • upgrade protobuf plugin to 0.7.7
  • add gradle 2.13 as gradle wrapper

Result:

  • we are aligned with latest plugin and gradle releases

Is there a way to intercept RuntimeException ?

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

SpringMvc integration

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.