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 { grpc } from "@gatling.io/grpc";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, which define a single server configuration and bind it to the gRPC protocol:
GrpcServerConfigurationBuilder exampleServer = grpc
.serverConfiguration("example")
.forAddress("host", 50051);
GrpcProtocolBuilder grpcProtocol = grpc
.serverConfigurations(exampleServer);
ScenarioBuilder scn = scenario("Scenario"); // etc.
setUp(
scn.injectOpen(atOnceUsers(1))
.protocols(grpcProtocol)
);const exampleServer = grpc
.serverConfiguration("example")
.forAddress("host", 50051);
const grpcProtocol = grpc
.serverConfigurations(exampleServer);
const scn = scenario("Scenario"); // etc.
setUp(
scn.injectOpen(atOnceUsers(1))
.protocols(grpcProtocol)
);val exampleServer = grpc
.serverConfiguration("example")
.forAddress("host", 50051)
val grpcProtocol = grpc
.serverConfigurations(exampleServer)
val scn = scenario("Scenario") // etc.
setUp(
scn.injectOpen(atOnceUsers(1))
.protocols(grpcProtocol)
)val exampleServer = grpc
.serverConfiguration("example")
.forAddress("host", 50051)
val grpcProtocol = grpc
.serverConfigurations(exampleServer)
val scn = scenario("Scenario") // etc.
setUp(
scn.inject(atOnceUsers(1))
.protocols(grpcProtocol)
)Server Configurations
Server configurations define the connection settings for one or more gRPC servers.
When multiple server configurations are defined, the first is used as the default unless explicitly overridden using the
serverConfiguration DSL method:
GrpcServerConfigurationBuilder exampleServer1 = grpc
.serverConfiguration("example1")
.forAddress("host", 50051);
GrpcServerConfigurationBuilder exampleServer2 = grpc
.serverConfiguration("example2")
.forAddress("host", 50052);
GrpcProtocolBuilder grpcProtocol = grpc
// exampleServer1 will serve as the default server configuration
.serverConfigurations(exampleServer1, exampleServer2);
ScenarioBuilder scn = scenario("Scenario"); // etc.
setUp(
scn.injectOpen(atOnceUsers(1))
.protocols(grpcProtocol)
);const exampleServer1 = grpc
.serverConfiguration("example1")
.forAddress("host", 50051);
const exampleServer2 = grpc
.serverConfiguration("example2")
.forAddress("host", 50052);
const grpcProtocol = grpc
// exampleServer1 will serve as the default server configuration
.serverConfigurations(exampleServer1, exampleServer2);
const scn = scenario("Scenario"); // etc.
setUp(
scn.injectOpen(atOnceUsers(1))
.protocols(grpcProtocol)
);val exampleServer1 = grpc
.serverConfiguration("example1")
.forAddress("host", 50051)
val exampleServer2 = grpc
.serverConfiguration("example2")
.forAddress("host", 50052)
val grpcProtocol = grpc
// exampleServer1 will serve as the default server configuration
.serverConfigurations(exampleServer1, exampleServer2)
val scn = scenario("Scenario") // etc.
setUp(
scn.injectOpen(atOnceUsers(1))
.protocols(grpcProtocol)
)val exampleServer1 = grpc
.serverConfiguration("example1")
.forAddress("host", 50051)
val exampleServer2 = grpc
.serverConfiguration("example2")
.forAddress("host", 50052)
val grpcProtocol = grpc
// exampleServer1 will serve as the default server configuration
.serverConfigurations(exampleServer1, exampleServer2)
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.
exampleServer.forAddress("host", 50051);exampleServer.forAddress("host", 50051);exampleServer.forAddress("host", 50051)exampleServer.forAddress("host", 50051)forTarget
Or by using a URL or FDQN with both host and port directly:
exampleServer.forTarget("dns:///host:50051");exampleServer.forTarget("dns:///host:50051");exampleServer.forTarget("dns:///host:50051")exampleServer.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:
exampleServer.shareChannel();exampleServer.shareChannel();exampleServer.shareChannel()exampleServer.shareChannelBe 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.
exampleServer.useChannelPool(4);exampleServer.useChannelPool(4);exampleServer.useChannelPool(4)exampleServer.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:
exampleServer.usePlaintext();exampleServer.usePlaintext();exampleServer.usePlaintext()exampleServer.usePlaintextuseInsecureTrustManager 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:
exampleServer.useInsecureTrustManager();exampleServer.useInsecureTrustManager();exampleServer.useInsecureTrustManager()exampleServer.useInsecureTrustManagerThis 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:
exampleServer.useStandardTrustManager();exampleServer.useStandardTrustManager();exampleServer.useStandardTrustManager()exampleServer.useStandardTrustManageruseCustomCertificateTrustManager
Or use your own certificate:
exampleServer.useCustomCertificateTrustManager("certificatePath");exampleServer.useCustomCertificateTrustManager("certificatePath");exampleServer.useCustomCertificateTrustManager("certificatePath")exampleServer.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.
exampleServer.shareSslContext();exampleServer.shareSslContext();exampleServer.shareSslContext()exampleServer.shareSslContextcallCredentials
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.
exampleServer
// 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);
});callCredentials DSL method is not supported by Gatling JS. We recommend using
asciiHeader or
binaryHeader with a
Gatling EL instead.exampleServer
// 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)
}exampleServer
// 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.
exampleServer
// 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);
});exampleServer
// 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) => {
const name = session.get("myUserName");
return channelCredentialsForUser(name);
});exampleServer
// 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)
}exampleServer
// 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:
exampleServer.channelCredentials(
TlsChannelCredentials.newBuilder()
.keyManager(
ClassLoader.getSystemResourceAsStream("client.crt"),
ClassLoader.getSystemResourceAsStream("client.key"))
.trustManager(ClassLoader.getSystemResourceAsStream("ca.crt"))
.build()
);exampleServer.channelCredentials({
rootCerts: "ca.crt",
certChain: "client.crt",
privateKey: "client.key"
});exampleServer.channelCredentials(
TlsChannelCredentials.newBuilder()
.keyManager(
ClassLoader.getSystemResourceAsStream("client.crt"),
ClassLoader.getSystemResourceAsStream("client.key"))
.trustManager(ClassLoader.getSystemResourceAsStream("ca.crt"))
.build()
)exampleServer.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.
exampleServer.overrideAuthority("test.example.com");exampleServer.overrideAuthority("test.example.com");exampleServer.overrideAuthority("test.example.com")exampleServer.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:
exampleServer
// 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"));exampleServer
// 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.get("headerValue"));exampleServer
// 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") }exampleServer
// 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:
exampleServer.asciiHeaders(Map.of("key", "value"));exampleServer.asciiHeaders({ key: "value" });exampleServer.asciiHeaders(
mapOf("key" to "value")
)exampleServer.asciiHeaders(
Map("key" -> "value")
)binaryHeader
Shortcut for a single header with the default binary marshaller, i.e.
io.grpc.Metadata#BINARY_BYTE_MARSHALLER:
exampleServer
// 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"));exampleServer
// with a static header value
.binaryHeader("key").value([118, 97, 108, 117, 101])
// with a Gatling EL string header value
.binaryHeader("key").valueEL("#{headerValue}")
// with a function value
.binaryHeader("key").value((session) => session.get("headerValue"));exampleServer
// 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") }exampleServer
// 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:
exampleServer.binaryHeaders(
Map.of("key", "value".getBytes(UTF_8))
);exampleServer.binaryHeaders({
key: [118, 97, 108, 117, 101]
});exampleServer.binaryHeaders(
mapOf("key" to "value".toByteArray(UTF_8))
)exampleServer.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);
exampleServer
// 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"));header DSL method is not supported by Gatling JS. We recommend using
asciiHeader or
binaryHeader instead.val key = Metadata.Key.of("key", Metadata.ASCII_STRING_MARSHALLER)
exampleServer
// 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)
exampleServer
// 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])Load 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:
exampleServer.useCustomLoadBalancingPolicy("pick_first");exampleServer.useCustomLoadBalancingPolicy("pick_first");exampleServer.useCustomLoadBalancingPolicy("pick_first")exampleServer.useCustomLoadBalancingPolicy("pick_first")Or with JSON configuration:
exampleServer.useCustomLoadBalancingPolicy("pick_first", "{}");exampleServer.useCustomLoadBalancingPolicy("pick_first", "{}");exampleServer.useCustomLoadBalancingPolicy("pick_first", "{}")exampleServer.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:
exampleServer.usePickFirstLoadBalancingPolicy();exampleServer.usePickFirstLoadBalancingPolicy();exampleServer.usePickFirstLoadBalancingPolicy()exampleServer.usePickFirstLoadBalancingPolicyusePickRandomLoadBalancingPolicy
Randomly pick an address from the name resolver:
exampleServer.usePickRandomLoadBalancingPolicy();exampleServer.usePickRandomLoadBalancingPolicy();exampleServer.usePickRandomLoadBalancingPolicy()exampleServer.usePickRandomLoadBalancingPolicyThis 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:
exampleServer.useRoundRobinLoadBalancingPolicy();exampleServer.useRoundRobinLoadBalancingPolicy();exampleServer.useRoundRobinLoadBalancingPolicy()exampleServer.useRoundRobinLoadBalancingPolicyOther settings
unmatchedInboundMessageBufferSize
By default, unmatched inbound messages are not buffered, you must enable this feature by setting the size of the buffer
on the protocol with .unmatchedInboundMessageQueueSize(maxSize):
exampleServer.unmatchedInboundMessageBufferSize(5);exampleServer.unmatchedInboundMessageBufferSize(5);exampleServer.unmatchedInboundMessageBufferSize(5)exampleServer.unmatchedInboundMessageBufferSize(5)Buffered messages can then be processed with
processUnmatchedMessages or awaitStreamEndAndProcessUnmatchedMessages.