|
| 1 | += Spring Cloud Gateway and gRPC |
| 2 | + |
| 3 | +Starting from `3.1.0-RC1`, Spring Cloud Gateway included support for gRPC and HTTP/2. |
| 4 | + |
| 5 | +We will introduce the basic concepts behind gRPC and how to configure it with two examples: |
| 6 | + |
| 7 | +* One that showcases how Spring Cloud Gateway can transparently re-route gRPC traffic without needing to know the proto definition and without having to modify our existing gRPC servers. |
| 8 | +
|
| 9 | +* Another that showcases how we can create a custom filter in Spring Cloud Gateway to transform a JSON payload to a gRPC message. |
| 10 | +
|
| 11 | +
|
| 12 | +== Introduction to gRPC and HTTP/2 |
| 13 | + |
| 14 | +HTTP/2 makes our applications faster, simpler, and more robust. Reducing latency by enabling request and response |
| 15 | +multiplexing, adding efficient compression of HTTP header fields, and adding support for request prioritization and |
| 16 | +server push. |
| 17 | + |
| 18 | +The reduction in the number of connections is particularly important when improving the performance of HTTPS: that way |
| 19 | +we have less expensive TLS handshakes, more efficient session reuse, reducing client and server resources. |
| 20 | + |
| 21 | +HTTP/2 provides two mechanisms for negotiating the application level protocol: |
| 22 | + |
| 23 | +* `H2C` HTTP/2.0 support with clear-text |
| 24 | +* `H2` HTTP/2.0 support with TLS |
| 25 | + |
| 26 | +Even though `reactor-netty` has support for `H2C` clear-text protocol, Spring Cloud Gateway requires `H2` with TLS to |
| 27 | +assure transport security. |
| 28 | + |
| 29 | +HTTP/2 adds a binary framing layer, which is how the HTTP messages are encapsulated and transferred between the client |
| 30 | +and server, enabling more efficient ways to transfer data. |
| 31 | + |
| 32 | +Thanks to https://github.com/reactor/reactor-netty[reactor-netty] and its HTTP/2 support, we were able to extend |
| 33 | +Spring Cloud Gateway to support gRPC. |
| 34 | + |
| 35 | +https://grpc.io/[gRPC] is a high-performance Remote Procedure Call framework that can run in any environment. It |
| 36 | +provides bi-directional streaming, and it's based on HTTP/2. |
| 37 | + |
| 38 | +gRPC services can be defined using Protocol Buffers, a powerful binary serialization toolset and language, and |
| 39 | +provides tools for generating clients and servers across different languages. |
| 40 | + |
| 41 | +== Getting started |
| 42 | + |
| 43 | +In order to enable gRPC in Spring Cloud Gateway, we need to enable HTTP/2 and SSL in our project https://docs.oracle.com/cd/E19830-01/819-4712/ablqw/index.html[by adding a keystore], this can be done |
| 44 | +through configuration by adding the following: |
| 45 | + |
| 46 | +[source,yaml] |
| 47 | +---- |
| 48 | +server: |
| 49 | + http2: |
| 50 | + enabled: true |
| 51 | + ssl: |
| 52 | + key-store-type: PKCS12 |
| 53 | + key-store: classpath:keystore.p12 |
| 54 | + key-store-password: password |
| 55 | + key-password: password |
| 56 | + enabled: true |
| 57 | +
|
| 58 | +---- |
| 59 | + |
| 60 | +Now that we have it enabled, we can create a route that redirects traffic to a gRPC server and take advantage of the |
| 61 | +existing filters and predicates, for example, this route will redirect traffic that comes from any path starting |
| 62 | +with `grpc` to a local server in the port `6565` and add header `X-Request-header` with the value `header-value`: |
| 63 | + |
| 64 | +[source,yaml] |
| 65 | +---- |
| 66 | +spring: |
| 67 | + cloud: |
| 68 | + gateway: |
| 69 | + routes: |
| 70 | + - id: grpc |
| 71 | + uri: https://localhost:6565 |
| 72 | + predicates: |
| 73 | + - Path=/grpc/** |
| 74 | + filters: |
| 75 | + - AddResponseHeader=X-Request-header, header-value |
| 76 | +---- |
| 77 | + |
| 78 | +== Running gRPC to gRPC |
| 79 | + |
| 80 | +An end to end example can be found in this repository with the following parts: |
| 81 | + |
| 82 | +image::grpc-simple-gateway.png[grpc-simple-gateway] |
| 83 | + |
| 84 | + |
| 85 | +* A `grpc-server` that exposes a `HelloService`, and gRPC endpoint to receive a `HelloRequest` and return |
| 86 | + a `HelloResponse`: |
| 87 | +[source,protobuf] |
| 88 | +---- |
| 89 | +syntax = "proto3"; |
| 90 | +
|
| 91 | +message HelloRequest { |
| 92 | + string firstName = 1; |
| 93 | + string lastName = 2; |
| 94 | +} |
| 95 | +
|
| 96 | +message HelloResponse { |
| 97 | + string greeting = 1; |
| 98 | +} |
| 99 | +
|
| 100 | +service HelloService { |
| 101 | + rpc hello(HelloRequest) returns (HelloResponse); |
| 102 | +} |
| 103 | +---- |
| 104 | + |
| 105 | +The server will concatenate a salutation with `firstName` and a `lastName` and respond with a `greeting`. |
| 106 | + |
| 107 | +For example, this input: |
| 108 | + |
| 109 | +[source,text] |
| 110 | +---- |
| 111 | +firstName: Saul |
| 112 | +lastName: Hudson |
| 113 | +---- |
| 114 | + |
| 115 | +Will output: |
| 116 | + |
| 117 | +[source,text] |
| 118 | +---- |
| 119 | +greeting: Hello, Saul Hudson |
| 120 | +---- |
| 121 | + |
| 122 | +* A `grpc-client`, in charge of sending the `HelloRequest` to the `HelloService`. |
| 123 | + |
| 124 | +* `grpc-simple-gateway` that routes the requests and adds a header with the configuration mentioned above. Note that this gateway application does not have any dependency to gRPC nor to the proto definition used by client and server. |
| 125 | + |
| 126 | +At the moment there is just one route that will forward everything to the `grpc-server`: |
| 127 | + |
| 128 | +[source,yaml] |
| 129 | +---- |
| 130 | + routes: |
| 131 | + - id: grpc |
| 132 | + uri: https://localhost:6565 |
| 133 | + predicates: |
| 134 | + - Path=/** |
| 135 | + filters: |
| 136 | + - AddResponseHeader=X-Request-header, header-value |
| 137 | +---- |
| 138 | + |
| 139 | +To start the server that is going to be listening to requests: |
| 140 | + |
| 141 | +[source,shell] |
| 142 | +---- |
| 143 | + ./gradlew :grpc-server:bootRun |
| 144 | +---- |
| 145 | + |
| 146 | +Then, we start the gateway that is going to re-route the gRPC requests: |
| 147 | + |
| 148 | +[source,shell] |
| 149 | +---- |
| 150 | +./gradlew :grpc-simple-gateway:bootRun |
| 151 | +---- |
| 152 | + |
| 153 | +Finally, we can use the client that points to the gateway application: |
| 154 | + |
| 155 | +[source,shell] |
| 156 | +---- |
| 157 | +./gradlew :grpc-client:bootRun |
| 158 | +---- |
| 159 | + |
| 160 | +The gateway routes and filters can be modified in `grpc-simple-gateway/src/main/resources/application.yaml` |
| 161 | + |
| 162 | +== Running JSON to gRPC with a custom filter |
| 163 | + |
| 164 | +Thanks to Spring Cloud Gateway flexibility, it is possible to create a custom filter to transform from a JSON payload to |
| 165 | +a gRPC message. |
| 166 | + |
| 167 | +Even though it will have a performance impact since we have to serialize and deserialize the requests in the gateway and creating a channel from it, |
| 168 | +it is a common pattern if you want to expose a JSON API while maintaining internal compatibility. |
| 169 | + |
| 170 | +For that, we can extend our `grpc-json-gateway` to include the `proto` definition with the message we want to send. |
| 171 | + |
| 172 | +image::grpc-json-gateway.png[grpc-json-gateway] |
| 173 | + |
| 174 | + |
| 175 | +Spring Cloud Gateway contains a mechanism to create custom filters allowing us to intercept requests and add custom logic to them. |
| 176 | + |
| 177 | +For this particular scenario, we are going to deserialize the JSON request and create a gRPC channel that will send a message to the `grpc-server`. |
| 178 | + |
| 179 | +[source,java] |
| 180 | +---- |
| 181 | +static class GRPCResponseDecorator extends ServerHttpResponseDecorator { |
| 182 | +
|
| 183 | + @Override |
| 184 | + public Mono<Void> writeWith(Publisher<?extends DataBuffer> body) { |
| 185 | + exchange.getResponse().getHeaders().set("Content-Type", "application/json"); |
| 186 | +
|
| 187 | + URI requestURI = exchange.getRequest().getURI(); |
| 188 | + ManagedChannel channel = createSecuredChannel(requestURI.getHost(), 6565); |
| 189 | +
|
| 190 | + return getDelegate().writeWith(deserializeJSONRequest() |
| 191 | + .map(jsonRequest -> { |
| 192 | + String firstName = jsonRequest.getFirstName(); |
| 193 | + String lastName = jsonRequest.getLastName(); |
| 194 | + return HelloServiceGrpc.newBlockingStub(channel) |
| 195 | + .hello(HelloRequest.newBuilder() |
| 196 | + .setFirstName(firstName) |
| 197 | + .setLastName(lastName) |
| 198 | + .build()); |
| 199 | + }) |
| 200 | + .map(this::serialiseJSONResponse) |
| 201 | + .map(wrapGRPCResponse()) |
| 202 | + .cast(DataBuffer.class) |
| 203 | + .last()); |
| 204 | + } |
| 205 | +} |
| 206 | +---- |
| 207 | + |
| 208 | +The full implementation can be found |
| 209 | +in: `grpc-json-gateway/src/main/java/com/example/grpcserver/hello/JSONToGRPCFilterFactory.java` |
| 210 | + |
| 211 | +Using the same `grpc-server`, we can start the gateway with the custom filter with: |
| 212 | + |
| 213 | +[source,shell] |
| 214 | +---- |
| 215 | +./gradlew :grpc-json-gateway:bootRun |
| 216 | +---- |
| 217 | + |
| 218 | +And send JSON requests to the `grpc-json-gateway` using, for example, `curl`: |
| 219 | + |
| 220 | +[source,bash] |
| 221 | +---- |
| 222 | +curl -XPOST 'https://localhost:8091/json/hello' -d '{"firstName":"Duff","lastName":"McKagan"}' -k -H"Content-Type: application/json" -v |
| 223 | +---- |
| 224 | + |
| 225 | +We see how the gateway application forwards the requests and returns the JSON payload with the new `Content-Type` header: |
| 226 | + |
| 227 | +[source,bash] |
| 228 | +---- |
| 229 | +< HTTP/2 200 |
| 230 | +< content-type: application/json |
| 231 | +< content-length: 34 |
| 232 | +< |
| 233 | +* Connection #0 to host localhost left intact |
| 234 | +{"greeting":"Hello, Duff McKagan"} |
| 235 | +---- |
| 236 | + |
| 237 | +== Next Steps |
| 238 | + |
| 239 | +In this post, we've looked at a few examples of how gRPC can be integrated within Spring Cloud Gateway. I’d love to know |
| 240 | +what other usages you've found to be helpful in your experiences. |
0 commit comments