GRPC Client
This section describes core concepts that Spring gRPC uses on the client side.
Create a Channel
You can inject a GrpcChannelFactory
into your application configuration and use it to create a gRPC channel.
The most common usage of a channel is to create a client that binds to a service.
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.
For example, consider the SimpleGrpc
service generated by the Protobuf tooling in your build.
To bind to this service on a local server:
@Bean
SimpleGrpc.SimpleBlockingStub stub(GrpcChannelFactory channels) {
return SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:9090"));
}
Shaded Netty Client
The default client implementation uses the Netty client.
You can switch to a shaded Netty implementation provided by the gRPC team by adding the grpc-netty-shaded
dependency and excluding the grpc-netty
dependency.
<dependency>
<groupId>org.springframework.grpc</groupId>
<artifactId>spring-grpc-spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
</dependency>
For Gradle users
dependencies {
implementation "org.springframework.grpc:spring-grpc-spring-boot-starter"
implementation 'io.grpc:grpc-netty-shaded'
modules {
module("io.grpc:grpc-netty") {
replacedBy("io.grpc:grpc-netty-shaded", "Use Netty shaded instead of regular Netty")
}
}
}
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.
|
Application Properties
The default GrpcChannelFactory
implementation can also 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"));
}
then in application.properties
:
spring.grpc.client.channels.local.address=0.0.0.0:9090
There is a default named channel that you can configure as spring.grpc.client.default-channel.*
, and then it will be used by default if there is no channel with the name specified in the channel creation.
The Local Server Port
If you are running a gRPC server locally as part of your application, you will often want to connect to it in an integration test.
It can be convenient in that case to use an ephemeral port for the server (spring.grpc.server.port=0
) and then use the port that is allocated to connect to it.
You can discover the port that the server is running on by injecting the @LocalGrpcPort
bean into your test.
The @Bean
has to be marked as @Lazy
to ensure that the port is available when the bean is created (it is only known when the server starts which is part of the startup process).
@Bean
@Lazy
SimpleGrpc.SimpleBlockingStub stub(GrpcChannelFactory channels, @LocalGrpcPort int port) {
return SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:" + port));
}
The channel can be configured via application.properties
as well, by using the `${local.grpc.port}
property placeholder.
The @Bean
where you create the stub must still be @Lazy
for the same reason as above.
For example:
spring.grpc.client.channels.local.address=0.0.0.0:${local.grpc.port}
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
customizer is applied prior to the globalExtraThingsInterceptor
.
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.
Mutual TLS
Mutual TLS (mTLS) is a security protocol that requires both the client and the server to present certificates to each other.
A Spring gRPC client can use mTLS by configuring the client in application.properties
.
The mechanism is through the use of SSL Bundles (from Spring Boot).
Here’s an example:
spring.grpc.client.channels.my-channel.ssl.bundle=sslclient
spring.grpc.client.channels.my-channel.negotiation-type=TLS
spring.ssl.bundle.jks.sslclient.keystore.location=classpath:client.jks
spring.ssl.bundle.jks.sslclient.keystore.password=secret
spring.ssl.bundle.jks.sslclient.keystore.type=JKS
spring.ssl.bundle.jks.sslclient.key.password=password
The first two lines configure a channel named my-channel
so that it has an SSL bundle named sslclient
.
The rest is the configuration of the SSL bundle itself, in this case using JKS encoding (other options are available).
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 HHTP 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 those interceptors to see how to create your own for custom headers.