gRPC Protocol
Learn about gRPC protocol settings, TLS, and load balancing
Bootstrapping
The Gatling gRPC DSL is not imported by default.
You have to manually add the following imports:
import io.gatling.javaapi.grpc.*;
import static io.gatling.javaapi.grpc.GrpcDsl.*;
import io.gatling.javaapi.grpc.*
import io.gatling.javaapi.grpc.GrpcDsl.*
import io.gatling.grpc.Predef._
Protocol
Use the grpc
object in order to create a gRPC protocol.
As with every protocol in Gatling, the gRPC protocol can be configured for a scenario. This is done thanks to the following statements:
GrpcProtocolBuilder grpcProtocol = grpc
.forAddress("host", 50051);
ScenarioBuilder scn = scenario("Scenario"); // etc.
setUp(
scn.injectOpen(atOnceUsers(1))
.protocols(grpcProtocol)
);
val grpcProtocol = grpc
.forAddress("host", 50051)
val scn = scenario("Scenario") // etc.
setUp(
scn.injectOpen(atOnceUsers(1))
.protocols(grpcProtocol)
)
val grpcProtocol = grpc
.forAddress("host", 50051)
val scn = scenario("Scenario") // etc.
setUp(
scn.inject(atOnceUsers(1))
.protocols(grpcProtocol)
)
Target required
The first and only mandatory step is to configure the remote service address and port.
forAddress
It can be done with either the address as host and port.
grpc.forAddress("host", 50051);
grpc.forAddress("host", 50051)
grpc.forAddress("host", 50051)
forTarget
Or by using a URL or FDQN with both host and port directly:
grpc.forTarget("dns:///host:50051");
grpc.forTarget("dns:///host:50051")
grpc.forTarget("dns:///host:50051")
Channel options
By default, every user will use their own gRPC channel to connect to the remote service.
shareChannel
It is possible to share the same channel for all users by using:
grpc.shareChannel();
grpc.shareChannel()
grpc.shareChannel
Be aware that even though the same channel is used for all users, the number of underlying connections used doesn’t scale automatically.
useChannelPool
Use a pool of gRPC channels, instead of a single channel. This allows opening more underlying HTTP/2 connections.
Useful when using shareChannel
and when performance is limited by the maximum number of concurrent gRPC streams open
on each connection.
grpc.useChannelPool(4);
grpc.useChannelPool(4)
grpc.useChannelPool(4)
Encryption and authentication
It is possible to use either unencrypted or encrypted connections to a remote service.
usePlaintext
If you don’t want the connection to be encrypted:
grpc.usePlaintext();
grpc.usePlaintext()
grpc.usePlaintext
useInsecureTrustManager
default
If you want to trust all certificates without any verification, you can use an insecure trust manager. A useful option for self-signed certificates:
grpc.useInsecureTrustManager();
grpc.useInsecureTrustManager()
grpc.useInsecureTrustManager
This is the default option as it is more performant and validating certificates typically isn’t important for load tests.
useStandardTrustManager
Finally, you can use the standard trust manager that comes with the JVM:
grpc.useStandardTrustManager();
grpc.useStandardTrustManager()
grpc.useStandardTrustManager
useCustomCertificateTrustManager
Or use your own certificate:
grpc.useCustomCertificateTrustManager("certificatePath");
grpc.useCustomCertificateTrustManager("certificatePath")
grpc.useCustomCertificateTrustManager("certificatePath")
shareSslContext
TLS handshake will be performed only once and the TLS sessions will be shared between all the users. Use this option if you want to avoid the overhead of TLS while still having per-user channels.
grpc.shareSslContext();
grpc.shareSslContext()
grpc.shareSslContext
callCredentials
You can specify call credentials by providing an instance of io.grpc.CallCredentials
. This will be applied to each
gRPC call, except when overridden on specific calls.
grpc
// with a constant
.callCredentials(callCredentials)
// or with an EL string to retrieve CallCredentials already stored in the session
.callCredentials("#{callCredentials}")
// or with a function
.callCredentials(session -> {
var name = session.getString("myUserName");
return callCredentialsForUser(name);
});
grpc
// with a constant
.callCredentials(callCredentials)
// or with an EL string to retrieve CallCredentials already stored in the session
.callCredentials("#{callCredentials}")
// or with a function
.callCredentials { session ->
val name = session.getString("myUserName")!!
callCredentialsForUser(name)
}
grpc
// with a constant
.callCredentials(callCredentials)
// or with an EL string to retrieve CallCredentials already stored in the session
.callCredentials("#{callCredentials}")
// or with a function
.callCredentials { session =>
val name = session("myUserName").as[String]
callCredentialsForUser(name)
}
channelCredentials
You can specify channel credentials by providing an instance of io.grpc.ChannelCredentials
.
grpc
// with a constant
.channelCredentials(channelCredentials)
// or with an EL string to retrieve CallCredentials already stored in the session
.channelCredentials("#{channelCredentials}")
// or with a function
.channelCredentials(session -> {
var name = session.getString("myUserName");
return channelCredentialsForUser(name);
});
grpc
// with a constant
.channelCredentials(channelCredentials)
// or with an EL string to retrieve CallCredentials already stored in the session
.channelCredentials("#{channelCredentials}")
// or with a function
.channelCredentials { session ->
val name = session.getString("myUserName")!!
channelCredentialsForUser(name)
}
grpc
// with a constant
.channelCredentials(channelCredentials)
// or with an EL string to retrieve CallCredentials already stored in the session
.channelCredentials("#{channelCredentials}")
// or with a function
.channelCredentials { session =>
val name = session("myUserName").as[String]
channelCredentialsForUser(name)
}
This is most often used for mutual auth TLS, for instance:
grpc.channelCredentials(
TlsChannelCredentials.newBuilder()
.keyManager(
ClassLoader.getSystemResourceAsStream("client.crt"),
ClassLoader.getSystemResourceAsStream("client.key"))
.trustManager(ClassLoader.getSystemResourceAsStream("ca.crt"))
.build()
);
grpc.channelCredentials(
TlsChannelCredentials.newBuilder()
.keyManager(
ClassLoader.getSystemResourceAsStream("client.crt"),
ClassLoader.getSystemResourceAsStream("client.key"))
.trustManager(ClassLoader.getSystemResourceAsStream("ca.crt"))
.build()
)
grpc.channelCredentials(
TlsChannelCredentials.newBuilder()
.keyManager(
ClassLoader.getSystemResourceAsStream("client.crt"),
ClassLoader.getSystemResourceAsStream("client.key"))
.trustManager(ClassLoader.getSystemResourceAsStream("ca.crt"))
.build()
)
Because io.grpc.ChannelCredentials
can specify its own trust manager, this option is not compatible with the
useInsecureTrustManager
, useStandardTrustManager
, or useCustomCertificateTrustManager
options.
To avoid the overhead of validating the server certificate, you can explicitly build your channel credentials with an insecure trust manager, for instance:
TlsChannelCredentials.newBuilder()
.trustManager(
io.netty.handler.ssl.util.InsecureTrustManagerFactory.INSTANCE.getTrustManagers()
)
TlsChannelCredentials.newBuilder()
.trustManager(
*io.netty.handler.ssl.util.InsecureTrustManagerFactory.INSTANCE.trustManagers
)
TlsChannelCredentials.newBuilder()
.trustManager(
io.netty.handler.ssl.util.InsecureTrustManagerFactory.INSTANCE.getTrustManagers:_*
)
overrideAuthority
You can override the authority used with TLS and HTTP virtual hosting.
grpc.overrideAuthority("test.example.com");
grpc.overrideAuthority("test.example.com")
grpc.overrideAuthority("test.example.com")
Headers
Define gRPC headers to be set on all requests. Note that keys in gRPC headers are allowed to be associated with more than one value, so adding the same key a second time will simply add a second value, not replace the first one.
asciiHeader
Shortcut for a single header with the default ASCII marshaller, i.e.
io.grpc.Metadata#ASCII_STRING_MARSHALLER
:
grpc
// with a static header value
.asciiHeader("key").value("value")
// with a Gatling EL string header value
.asciiHeader("key").valueEl("#{headerValue}")
// with a function value
.asciiHeader("key").value(session -> session.getString("headerValue"));
grpc
// with a static header value
.asciiHeader("key").value("value")
// with a Gatling EL string header value
.asciiHeader("key").valueEl("#{headerValue}")
// with a function value
.asciiHeader("key").value { session -> session.getString("headerValue") }
grpc
// with a static header value
.asciiHeader("key")("value")
// with a Gatling EL string header value
.asciiHeader("key")("#{headerValue}")
// with a function value
.asciiHeader("key")(session => session("headerValue").as[String])
asciiHeaders
Shortcut for multiple headers with the default ASCII marshaller as a map of multiple key and
value pairs, i.e. io.grpc.Metadata#ASCII_STRING_MARSHALLER
:
grpc.asciiHeaders(Map.of("key", "value"));
grpc.asciiHeaders(
mapOf("key" to "value")
)
grpc.asciiHeaders(
Map("key" -> "value")
)
binaryHeader
Shortcut for a single header with the default binary marshaller, i.e.
io.grpc.Metadata#BINARY_BYTE_MARSHALLER
:
grpc
// with a static header value
.binaryHeader("key").value("value".getBytes(UTF_8))
// with a Gatling EL string header value
.binaryHeader("key").valueEl("#{headerValue}")
// with a function value
.binaryHeader("key").value(session -> session.get("headerValue"));
grpc
// with a static header value
.binaryHeader("key").value("value".toByteArray(UTF_8))
// with a Gatling EL string header value
.binaryHeader("key").valueEl("#{headerValue}")
// with a function value
.binaryHeader("key").value { session -> session.get("headerValue") }
grpc
// with a static header value
.binaryHeader("key")("value".getBytes(UTF_8))
// with a Gatling EL string header value
.binaryHeader("key")("#{headerValue}")
// with a function value
.binaryHeader("key")(session => session("headerValue").as[Array[Byte]])
binaryHeaders
Shortcut for multiple headers with the default binary marshaller as a map of multiple key and
pairs, i.e. io.grpc.Metadata#BINARY_BYTE_MARSHALLER
:
grpc.binaryHeaders(Map.of("key", "value".getBytes(UTF_8)));
grpc.binaryHeaders(
mapOf("key" to "value".toByteArray(UTF_8))
)
grpc.binaryHeaders(
Map("key" -> "value".getBytes(UTF_8))
)
header
Add a single header with a custom key.
var key = Metadata.Key.of("key", Metadata.ASCII_STRING_MARSHALLER);
grpc
// with a static header value
.header(key).value("value")
// with a Gatling EL string header value
.header(key).valueEl("#{headerValue}")
// with a function value
.header(key).value(session -> session.get("headerValue"));
val key = Metadata.Key.of("key", Metadata.ASCII_STRING_MARSHALLER)
grpc
// with a static header value
.header(key).value("value")
// with a Gatling EL string header value
.header(key).valueEl("#{headerValue}")
// with a function value
.header(key).value { session -> session.get("headerValue") }
val key = Metadata.Key.of("key", Metadata.ASCII_STRING_MARSHALLER)
grpc
// with a static header value
.header[String](key)("value")
// with a Gatling EL string header value
.header[String](key)("#{headerValue}")
// with a function value
.header(key)(session => session("headerValue").as[String])
Loading balancing
When the name resolver returns a list of several service IP addresses, you probably want to configure a load balancing policy. The policy is responsible for maintaining connections to the services and picking a connection to use each time a request is sent.
useCustomLoadBalancingPolicy
Use a custom load balancing by name:
grpc.useCustomLoadBalancingPolicy("pick_first");
grpc.useCustomLoadBalancingPolicy("pick_first")
grpc.useCustomLoadBalancingPolicy("pick_first")
Or with JSON configuration:
grpc.useCustomLoadBalancingPolicy("pick_first", "{}");
grpc.useCustomLoadBalancingPolicy("pick_first", "{}")
grpc.useCustomLoadBalancingPolicy("pick_first", "{}")
Check the gRPC documentation for more details.
usePickFirstLoadBalancingPolicy
This policy actually does no load balancing but just tries each address it gets from the name resolver and uses the first one it can connect to:
grpc.usePickFirstLoadBalancingPolicy();
grpc.usePickFirstLoadBalancingPolicy()
grpc.usePickFirstLoadBalancingPolicy
usePickRandomLoadBalancingPolicy
Randomly pick an address from the name resolver:
grpc.usePickRandomLoadBalancingPolicy();
grpc.usePickRandomLoadBalancingPolicy()
grpc.usePickRandomLoadBalancingPolicy
This load balancing policy is bundled with Gatling gRPC but not a standard of gRPC.
useRoundRobinLoadBalancingPolicy
Round-robin load balancing over the addresses returned by the name resolver:
grpc.useRoundRobinLoadBalancingPolicy();
grpc.useRoundRobinLoadBalancingPolicy()
grpc.useRoundRobinLoadBalancingPolicy