spring-cloud-gateway
spring-cloud-gateway copied to clipboard
spring-cloud-gateway grpc supports H2C
Our business scenarios need to support non-SSL mode transmission,That means we need to support H2C
Could you give us some suggestions and tell us how to modify so that spring-cloud-gateway can support H2C?
Many thanks
I was able to configure gateway to do both HTTP1.1 & H2C:
@Configuration
public class H2CConfiguration {
// https://projectreactor.io/docs/netty/release/reference/index.html#_http2
@Bean
public NettyWebServerFactoryCustomizer h2cServerCustomizer(Environment environment, ServerProperties serverProperties) {
return new NettyWebServerFactoryCustomizer(environment, serverProperties) {
@Override
public void customize(NettyReactiveWebServerFactory factory) {
factory.addServerCustomizers(httpServer -> httpServer.protocol(HttpProtocol.HTTP11, HttpProtocol.H2C));
super.customize(factory);
}
};
}
}
@Component
public class H2CAwareNettyRoutingFilter extends NettyRoutingFilter {
public H2CAwareNettyRoutingFilter(HttpClient httpClient, ObjectProvider<List<HttpHeadersFilter>> headersFiltersProvider, HttpClientProperties properties) {
super(httpClient, headersFiltersProvider, properties);
}
@Override
protected HttpClient getHttpClient(Route route, ServerWebExchange exchange) {
HttpClient httpClient = super.getHttpClient(route, exchange);
boolean h2cRoute = ofNullable(route.getMetadata().get("h2c"))
.map(o -> (Boolean)o)
.orElse(false);
if (h2cRoute) {
// https://projectreactor.io/docs/netty/release/reference/index.html#_http2_2
return httpClient.protocol(HttpProtocol.H2C);
} else {
return httpClient;
}
}
@Override
public int getOrder() {
return super.getOrder() - 1;
}
}
spring:
application:
name: gateway
cloud:
gateway:
routes:
- predicates:
- Path=/h2c
uri: http://127.0.0.1:8088
metadata:
h2c: true
- predicates:
- Path=/http
uri: http://127.0.0.1:8089
See here for full project.
I also tried to set both HTTP1.1 & H2C in client
@Bean
public HttpClientCustomizer h2ClientCustomizer() {
return httpClient -> httpClient.protocol(HttpProtocol.HTTP11, HttpProtocol.H2C);
}
but it isn't working, seems it requires to dig deeper in reactor-netty
we also encountered the same problem (we both set h2c and h1 in spring cloud gateway , and use grpc server) and got answers from relevant communities: grpc-java reactor-netty
so we first make it clear that there are four protocl:
- h2 with tls/ssl;
- h2 without tls/ssl;
- Negotiable H2,no tls/ssl
- http1.1
for protocol No.3 it is a negotiation protocol based on http1.1 and just carries keywords such as upgrade in some positions such as the header.
And it's clear from https://projectreactor.io/docs/netty/release/reference/index.html#_protocol_selection_2, for reactor-netty
- only set h2c protocol, it's will use h2 without tls/ssl (above 2);
- both set h2c and http1, it's will use h2 upgrade form http1(above 3);
in some doc and our test case about protocol No3 :
- if server support protocol No2 and protocol No3, client request with protocol No3 ,server side will reply status code 101, then client and server will use H2 without tls/ssl.
- if server only support H1, server will ignore the keywords , then client and server will still use htttp1.1
- if server support H2, not support H2C (like grpc), servr side will close the channel.
we reslove this problem like @nkonev's code, but we define two independent clients instead of modifying the protocol when using:one client set h1 protocol ,and other one set h2c protocol. decide which client to use according to the header (H2 has a unique header) or business-related routing, etc.
by the way, I suspectused like this, it may cause the code return httpClient; use of H2 protocol if httpclient base on pool
if (h2cRoute) { // https://projectreactor.io/docs/netty/release/reference/index.html#_http2_2 return httpClient.protocol(HttpProtocol.H2C); } else { return httpClient; }
@sodaRyCN I'm facing the same issue, can you provide some code sample for that ?
Analogous configurations for what is supported on the server side could be added for client site. Right now, only H2 is supported because ssl is assumed https://github.com/spring-cloud/spring-cloud-gateway/blob/be2abff70ea68b2bc7a1bd75bc6e0af74f45e9b2/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientFactory.java#L86.
@jasonhao518 u cloud refer to the following code, if you have a better implementation, please share. Also strongly recommend reading my previous review carefully.
@Configuration(proxyBeanMethods = false)
//Indicates that the scg server supports H2 protocol without tls
@ConditionalOnExpression("${server.http2.enabled:false} && !${server.ssl.enabled:true}")
@ConditionalOnClass({DispatcherHandler.class})
@AutoConfigureAfter({GatewayNoLoadBalancerClientAutoConfiguration.class)
public class GrpcAutoConfiguration {
@Bean("grpcHttpClientCustomizer")
public GrpcHttpClientCustomizer grpcHttpClientCustomizer(
KeeperClientEventLoop clientEventLoopForGrpc) {
return httpClient -> httpClient.protocol(HttpProtocol.H2C);
}
@Bean("grpcHttpClient")
public GrpcHttpClient grpcHttpClient(HttpClientProperties properties, GrpcHttpClientCustomizer grpcHttpClientCustomizer) {
return GrpcHttpClient.GrpcHttpClientBuilder.config(properties, grpcHttpClientCustomizer).create();
}
@Bean
@ConditionalOnMissingClass
public GrpcRoutingFilter grpcRoutingFilter(GrpcHttpClient grpcHttpClient,
ObjectProvider<List<HttpHeadersFilter>> headersFilters,
HttpClientProperties properties) {
return new GrpcRoutingFilter(grpcHttpClient, headersFilters, properties);
}
@Bean("grpcRouteBuilder")
public RouteLocatorBuilder.Builder grpcRouteBuilder(RouteLocatorBuilder builder, GrpcRoutingFilter grpcRoutingFilter) {
return builder.routes()
.route("grpc", r -> r.header("Content-Type", "^(application/grpc)( *;.*)*?$")
.filters(f -> f.filters(grpcRoutingFilter))
.uri("xxxx"));
}
@Bean("grpcRouteLocator")
public RouteLocator routeLocator(RouteLocatorBuilder.Builder grpcRouteBuilder) {
return grpcRouteBuilder.build();
}
}
public class GrpcRoutingFilter implements GatewayFilter, Ordered {
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
....
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({DispatcherHandler.class})
@AutoConfigureAfter({GatewayNoLoadBalancerClientAutoConfiguration.class)
@EnableWebFluxSecurity
public class HttpAutoConfiguration {
@Bean
public HttpClientCustomizer httpClientCustomizer(KeeperClientEventLoop clientEventLoop) {
return httpClient -> httpClient.protocol(HttpProtocol.HTTP11);
}
@Bean("baseRouteBuilder")
public RouteLocatorBuilder.Builder baseRouteBuilder(RouteLocatorBuilder builder, NettyRoutingFilter nettyRoutingFilter) {
return builder.routes()
.route("http", r -> r.path("/xx/xx").filters(f -> f.filters(nettyRoutingFilter))
.uri("xxx"));
}
@Bean("baseRouteLocator")
public RouteLocator routeLocator(RouteLocatorBuilder.Builder baseRouteBuilder) {
return baseRouteBuilder.build();
}
}
public class NettyRoutingFilter implements GatewayFilter, Ordered {
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
....
}
}
There is a simple solution, although it is not very reasonable.
- Copy the source code of
org.springframework.cloud.gateway.config.HttpClientFactoryto your project, making sure it is in the same package. - Refer to the modifications below, which only support H2C.
- Since the
target/classesof the application take precedence over the jar, modifying the version of HttpClientFactory is more prioritized.
@Override
protected HttpClient createInstance() {
// configure pool resources
ConnectionProvider connectionProvider = buildConnectionProvider(properties);
HttpClient httpClient = HttpClient.create(connectionProvider)
// TODO: move customizations to HttpClientCustomizers
.httpResponseDecoder(this::httpResponseDecoder);
if (serverProperties.getHttp2().isEnabled()) {
httpClient = httpClient.protocol(HttpProtocol.HTTP11, HttpProtocol.H2);
}
if (properties.getConnectTimeout() != null) {
httpClient = httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, properties.getConnectTimeout());
}
httpClient = configureProxy(httpClient);
httpClient = configureSsl(httpClient);
change into:
@Override
protected HttpClient createInstance() {
// configure pool resources
ConnectionProvider connectionProvider = buildConnectionProvider(properties);
HttpClient httpClient = HttpClient.create(connectionProvider)
// TODO: move customizations to HttpClientCustomizers
.httpResponseDecoder(this::httpResponseDecoder);
if (serverProperties.getHttp2().isEnabled()) {
httpClient = httpClient.protocol(HttpProtocol.H2C);
}
if (properties.getConnectTimeout() != null) {
httpClient = httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, properties.getConnectTimeout());
}
httpClient = configureProxy(httpClient);
// httpClient = configureSsl(httpClient);
I was able to make it work as @nkonev suggested, is there still no OOTB solution for this?
@jianmo1997 Hello, I have also encountered the same problem. Have you solved it so far?