|
This version is still in development and is not considered stable yet. For the latest stable version, please use Spring gRPC 1.0.3! |
GRPC Client
This section describes core concepts that Spring gRPC uses on the client side. The Protobuf-generated sources in your project will contain the stub classes, and they just need to be bound to a channel. The Protobuf files will be provided by the service you are connecting to.
Channel Factory
You can create and inject a GrpcChannelFactory into your application configuration and use it to create a gRPC channel.
The default GrpcChannelFactory implementation can create a "named" channel, which you can then use to extract the configuration to connect to the server.
For example:
@Bean
SimpleGrpc.SimpleBlockingStub stub(GrpcChannelFactory channels) {
return SimpleGrpc.newBlockingStub(channels.createChannel("local"));
}
The "local" channel is just an example name. You can use any name you like for your channels. It is resolved by a VirtualTargets implementation that looks up the configuration for the channel and is injected into the channel factory.
Automatic Client Configuration
The automatic creation of beans for gRPC stubs is switched on by the @ImportGrpcClients annotation.
You can add this annotation to change the base type of the stubs or to scan a different package.
Spring gRPC will scan the application packages for gRPC stubs and automatically create bean definitions.
Package Scanning
The @ImportGrpcClients annotation can be used to control the scan for gRPC stub implementations.
To scan a package you can specify the basePackages or basePackageClasses attribute.
Then elsewhere in the application you can @Autowired the generated gRPC stubs (the blocking sub-type by default).
You can change the factory used to create the stubs from BlockingStubFactory by setting the factory attribute.
There are standard factories pre-registered for common stub types, and if you want to register additional factories you can add a bean of type StubFactory (see below for details).
The default behaviour in a Spring Boot application is equivalent to the following configuration on your @SpringBootApplication class:
// This is the default behaviour, so not necessary to add this annotation unless you change its attributes
@ImportGrpcClients(basePackageClasses = MyApplication.class)
@SpringBootApplication
class MyApplication {
// ...
}
You can enhance and modify the configuration by providing spring.grpc.client.* application properties or by defining your own GrpcChannelBuilderCustomizer beans.
More Complex Examples
A GrpcChannelBuilderCustomizer can also control the creation of the channels and add custom behaviour to stubs (individually or via a scan).
For example, to add a custom security interceptor to only clients using the "stub" channel:
@ImportGrpcClients(target = "stub", prefix = "secure", types = SimpleBlockingStub.class)
@Configuration
class ExtraConfiguration {
@Bean
GrpcChannelBuilderCustomizer<?> stubs() {
return GrpcChannelBuilderCustomizer.matches("stub", builder ->
builder.intercept(new BearerTokenAuthenticationInterceptor(() -> token(context))));
}
}
In this example, instead of scanning for all stubs, we register a specific stub class SimpleBlockingStub with the channel named stub.
The prefix secure is used as a bean definition name prefix, so the resulting bean definition in this case is "secureSimpleBlockingStub".
This feature is useful when you want to have multiple instances of the same stub class with different configurations.
If you have a custom StubFactory then add it as a @Bean and ensure that the bean definition has the correct concrete type. Then refer to that in the @ImportGrpcClients as its factory type. A custom factory type usually has a static method supports(Class<?> type) returning a boolean indicating whether the factory can create a stub of the given type. If it does not have the static method, then the factory will be used for all explicit stub types listed (but it cannot be used in a scan). The supports method has to be
static because it is used before the factory is created, to generate bean definitions for the stubs.
Create a Client Manually
Instead of using the @ImportGrpcClients or GrpcClientFactory features, we can create a client @Bean manually.
The most common usage of a channel is to create a client that binds to a service.
For example:
@Bean
SimpleGrpc.SimpleBlockingStub stub(GrpcChannelFactory channels) {
return SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:9090"));
}
Channel Configuration
The channel factory provides an API to create channels. The channel creation process can be configured as follows.
Channel Builder Customizer
The ManagedChannelBuilder used by the factory to create the channel can be customized prior to channel creation.
Global
To customize the builder used for all created channels you can register one more GrpcChannelBuilderCustomizer beans.
The customizers are applied to the auto-configured GrpcChannelFactory in order according to their bean natural ordering (i.e. @Order).
@Bean
@Order(100)
GrpcChannelBuilderCustomizer<NettyChannelBuilder> flowControlCustomizer() {
return (name, builder) -> builder.flowControlWindow(1024 * 1024);
}
@Bean
@Order(200)
<T extends ManagedChannelBuilder<T>> GrpcChannelBuilderCustomizer<T> retryChannelCustomizer() {
return (name, builder) -> builder.enableRetry().maxRetryAttempts(5);
}
In the preceding example, the flowControlCustomizer customizer is applied prior to the retryChannelCustomizer.
Furthermore, the flowControlCustomizer is only applied if the auto-configured channel factory is a NettyGrpcChannelFactory.
Per-channel
To customize an individual channel you can specify a GrpcChannelBuilderCustomizer on the options passed to the factory during channel creation.
The per-channel customizer will be applied after any global customizers.
@Bean
SimpleGrpc.SimpleBlockingStub stub(GrpcChannelFactory channelFactory) {
ChannelBuilderOptions options = ChannelBuilderOptions.defaults()
.withCustomizer((__, b) -> b.disableRetry());
ManagedChannel channel = channelFactory.createChannel("localhost", options);
return SimpleGrpc.newBlockingStub(channel);
}
The above example disables retries for the single created channel only.
While the channel builder customizer gives you full access to the native channel builder, you should not call build on the customized builder as the channel factory handles the build call for you and doing so will create orphaned channels.
|
Client Interceptors
Global
To add a client interceptor to be applied to all created channels you can simply register a client interceptor bean and then annotate it with @GlobalClientInterceptor.
When you register multiple interceptor beans they are ordered according to their bean natural ordering (i.e. @Order).
@Bean
@Order(100)
@GlobalClientInterceptor
ClientInterceptor globalLoggingInterceptor() {
return new LoggingInterceptor();
}
@Bean
@Order(200)
@GlobalClientInterceptor
ClientInterceptor globalExtraThingsInterceptor() {
return new ExtraThingsInterceptor();
}
In the preceding example, the globalLoggingInterceptor is applied prior to the globalExtraThingsInterceptor.
Filtering
All global interceptors are applied to all created channels by default.
However, you can register a ClientInterceptorFilter to decide which interceptors are applied to which channel factories.
The following example prevents the ExtraThingsInterceptor interceptor from being applied to any channels created by the InProcessGrpcChannelFactory channel factory.
@Bean
ClientInterceptorFilter myInterceptorFilter() {
return (interceptor, factory) -> !(interceptor instanceof ExtraThingsInterceptor
&& factory instanceof InProcessGrpcChannelFactory);
}
The InProcessGrpcChannelFactory picks up the ClientInterceptorFilter bean automatically and applies it to the global interceptors.
For other channel factories, register a bean of type java.util.function.Consumer<org.springframework.grpc.client.GrpcChannelFactory> that casts to DefaultGrpcChannelFactory (or another concrete implementation) and calls setInterceptorFilter with your filter.
Per-Channel
To add one or more client interceptors to be applied to a single client channel you can simply set the interceptor instance(s) on the options passed to the channel factory when creating the channel.
@Bean
SimpleGrpc.SimpleBlockingStub stub(GrpcChannelFactory channelFactory) {
ClientInterceptor interceptor1 = getChannelInterceptor1();
ClientInterceptor interceptor2 = getChannelInterceptor2();
ChannelBuilderOptions options = ChannelBuilderOptions.defaults()
.withInterceptors(List.of(interceptor1, interceptor2));
ManagedChannel channel = channelFactory.createChannel("localhost", options);
return SimpleGrpc.newBlockingStub(channel);
}
The above example applies interceptor1 then interceptor2 to the single created channel.
While the channel builder customizer gives you full access to the native channel builder, we recommend not calling intercept on the customized builder but rather set the per-channel interceptors using the ChannelBuilderOptions as described above.
If you do call intercept directly on the builder then those interceptors will be applied before the above described global and per-channel interceptors.
|
Blended
When a channel is constructed with both global and per-channel interceptors, the global interceptors are first applied in their sorted order followed by the per-channel interceptors in their sorted order.
However, by setting the withInterceptorsMerge parameter on the ChannelBuilderOptions passed to the channel factory to "true" you can change this behavior so that the interceptors are all combined and then sorted according to their bean natural ordering (i.e. @Order or Ordered interface).
You can use this option if you want to add a per-client interceptor between global interceptors.
The per-channel interceptors you pass in must either be bean instances marked with @Order or regular objects that implement the Ordered interface to be properly merged/ordered with the global interceptors.
|
Observability
Spring gRPC provides an autoconfigured interceptor that can be used to provide observability to your gRPC clients.
Security
If your remote gRPC server expects requests to be authenticated you will need to configure the client to provide authentication credentials.
HTTP Headers
Spring gRPC provides a couple of interceptor that can be used to provide security to your gRPC clients. There is one for Basic HTTP authentication and one for OAuth2 (bearer tokens). Here’s an example of creating a channel that uses Basic HTTP authentication:
@Bean
@Lazy
Channel basic(GrpcChannelFactory channels) {
return channels.createChannel("my-channel", ChannelBuilderOptions.defaults()
.withInterceptors(List.of(new BasicAuthenticationInterceptor("user", "password"))));
}
Usage of the bearer token interceptor is similar.
You can look at the implementation of BasicAuthenticationInterceptor and BearerTokenAuthenticationInterceptor to see how to create your own for custom headers.
OAuth2 Clients
Spring gRPC provides an autoconfigured OAuth2 client that can be used to provide authentication to your gRPC clients.
It works the same as in any Spring Boot application, in that if you configure properties in spring.security.oauth2.authorizationserver.client.* you will be able to inject an ClientRegistrationRepository and use it to create an OAuth2AuthorizedClient for a given client registration.
Here’s an example showing how to plug the client registration into a BearerTokenAuthenticationInterceptor in the gRPC client:
@Bean
@Lazy
SimpleGrpc.SimpleBlockingStub basic(GrpcChannelFactory channels, ClientRegistrationRepository registry) {
ClientRegistration reg = registry.findByRegistrationId("spring");
return SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:9090", ChannelBuilderOptions.defaults()
.withInterceptors(List.of(new BearerTokenAuthenticationInterceptor(() -> token(reg))))));
}
private String token(ClientRegistration reg) {
RestClientClientCredentialsTokenResponseClient creds = new RestClientClientCredentialsTokenResponseClient();
String token = creds.getTokenResponse(new OAuth2ClientCredentialsGrantRequest(reg))
.getAccessToken()
.getTokenValue();
return token;
}