我正在尝试使用 Spring Cloud Gateway 和 k8s 客户端发现来实现网关服务,它将重定向 http/1.1 和 http/2(GRPC) 请求
我设置了以下配置:
server:
port: 9995
shutdown: graceful
http2:
enabled: true
logging:
level:
org.springframework.cloud.gateway: TRACE
spring:
application:
name: api-gateway
lifecycle:
timeout-per-shutdown-phase: 30s
cloud:
gateway:
discovery:
locator:
enabled: true
include-expression: "metadata['gateway.enabled']=='true'"
predicates:
- name: Path
args:
pattern: "'/'+serviceId+'/**'"
filters:
- name: RewritePathAndPort
args:
grpcPort: 9090
httpPort: 8080
kubernetes:
discovery:
enabled: true
primary-port-name: app-port
loadbalancer:
enabled: true
mode: service
# port-name: grpc-port
management:
endpoint:
health:
enabled: true
show-details: always
gateway:
enabled: true
endpoints:
web:
exposure:
include: '*'
我还实现了自定义过滤器
RewritePathAndPort
:
@Component
class RewritePathAndPortGatewayFilterFactory :
AbstractGatewayFilterFactory<RewritePathAndPortGatewayFilterFactory.Config>(Config::class.java) {
val log = KotlinLogging.logger {}
class Config {
var grpcPort: Int = 9090
var httpPort: Int = 8080
}
override fun apply(config: Config): GatewayFilter =
GatewayFilter { exchange, chain ->
val req = exchange.request
ServerWebExchangeUtils.addOriginalRequestUrl(exchange, req.uri)
val path = req.uri.rawPath
val pathSegments = path.split("/")
log.info { "pathSegments: $pathSegments" }
val host = pathSegments[1]
log.info { "new host: $host" }
val (newPath, newPort) =
if (path.contains("/grpc/")) {
stripPathSegments(pathSegments = pathSegments, segmentsToStrip = 3) to config.grpcPort
} else {
path to config.httpPort
}
log.info { "new port: $newPort" }
log.info { "new path: $newPath" }
val newUri =
UriComponentsBuilder
.fromUri(req.uri)
.scheme("lb")
.host(host)
.port(newPort) // Set the new port
.replacePath(newPath) // Set the new path
.build(true) // true for encoding the path
.toUri()
log.info { "new Uri: $newUri" }
val request =
req
.mutate()
.uri(newUri)
.path(newPath)
.build()
log.info { "new request: $request" }
exchange.attributes[ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR] = request.uri
log.info { "new exchange: $exchange" }
chain.filter(exchange.mutate().request(request).build())
}
override fun shortcutFieldOrder(): List<String> = listOf("regexp", "replacement", "port")
private fun stripPathSegments(
pathSegments: List<String>,
segmentsToStrip: Int,
): String =
pathSegments
.subList(segmentsToStrip, pathSegments.size)
.joinToString("/")
.let { "/$it" }
}
说明:
如果是http请求:
例如带有路径的请求:
/other-service/api/v1/hello
uri 将是 lb://other-service:8080/other-service/api/v1/hello
如果是grpc请求:
例如:
/other-grpc-service/grpc/HelloService/sayHello
uri 将是 lb://other-grpc-service:9090/HelloService/sayHello
通过上述配置,http 请求可以正常工作,而 grpc 则不能(失败
Connection prematurely closed BEFORE response
)
因为负载均衡器使用 http 服务上配置的默认 http 端口 8080
如果我取消注释
port-name: grpc-port
,它告诉负载均衡器使用端口 grpc-port
,grpc 请求正在工作,但 http 请求会因相同的错误而失败。
发生这种情况是因为负载均衡器使用在 grpc 服务(9090)上配置的
grpc-port
覆盖 uri 端口
造成这种情况的原因是:
ReactiveLoadBalancerClientFilter
。
有什么办法可以解决这个问题吗?
编辑:经过几个小时的尝试,我找到了解决方法。我基本上复制了
ReactiveLoadBalancerClientFilter
并根据内容类型标头更改了端口并且它起作用了,但这不太理想
要使用 Spring Cloud Gateway 和 Kubernetes 发现客户端来针对 HTTP/1.1 和 gRPC 请求实现网关服务,请按照以下步骤操作:
1。 application.yml中的配置:
server:
port: 9995
shutdown: graceful
http2:
enabled: true
logging:
level:
org.springframework.cloud.gateway: TRACE
spring:
application:
name: api-gateway
lifecycle:
timeout-per-shutdown-phase: 30s
cloud:
gateway:
discovery:
locator:
enabled: true
include-expression: "'metadata[''gateway.enabled''] == ''true'''"
routes:
- id: grpc-route
uri: lb://GRPC-SERVICE
predicates:
- Path=/grpc/**
filters:
- RewritePath=/grpc/(?<segment>.*), /${segment}
- CustomGRPCFilter
- id: http-route
uri: lb://HTTP-SERVICE
predicates:
- Path=/http/**
filters:
- RewritePath=/http/(?<segment>.*), /${segment}
- CustomHTTPFilter
kubernetes:
discovery:
enabled: true
primary-port-name: http
2。自定义过滤器实现:
@Component
public class RewritePathAndPortGatewayFilterFactory extends AbstractGatewayFilterFactory<RewritePathAndPortGatewayFilterFactory.Config> {
private static final Logger log = LoggerFactory.getLogger(RewritePathAndPortGatewayFilterFactory.class);
public RewritePathAndPortGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerWebExchangeUtils.addOriginalRequestUrl(exchange, exchange.getRequest().getURI());
String path = exchange.getRequest().getURI().getRawPath();
String newPath = path.replaceAll("/grpc/", "/");
if (path.startsWith("/grpc/")) {
newPath = newPath + ":" + config.grpcPort;
} else {
newPath = newPath + ":" + config.httpPort;
}
log.info("New path: {}", newPath);
URI newUri = UriComponentsBuilder.fromUri(exchange.getRequest().getURI())
.replacePath(newPath)
.build()
.toUri();
ServerHttpRequest request = exchange.getRequest().mutate()
.uri(newUri)
.build();
return chain.filter(exchange.mutate().request(request).build());
};
}
public static class Config {
private int grpcPort = 9090;
private int httpPort = 8080;
// Getters and setters
}
}
3. Kubernetes 发现配置: 确保您的 Kubernetes 服务已正确注释以供发现。包含表明它们应该是网关一部分的元数据。
4。日志记录和调试: 为 org.springframework.cloud.gateway 启用 TRACE 日志记录以监视请求转换并确保正确重写路径和端口。
我希望这些步骤将帮助您设置网关服务以有效处理 HTTP 和 gRPC 请求。