背景
基于Spring Gateway和Eureka 结合的微服务开发方式,
如果使用自动路由解析,可以将微服务上的eureka服务ID当成路由的key,从而能够根据网关地址+服务ID实现服务的自动转发。
Spring Gateway这么配置即可:
spring:
application:
name: gateway
cloud:
gateway:
enabled: true
discovery:
locator:
enabled: true
lower-case-service-id: true
例如一个微服务的spring.application.name=my-service
,其中有一个接口地址为/api/api-test
。 则通过http://网关地址:端口/my-service/api/api-test
就可以转发到这个微服务上。
然而,因为某种原因,这个微服务加了server.servlet.context-path=/my-service
。这样的话就不能直接是用自动路由了。而必须手动的添加路由策略:
spring:
name: gateway
cloud:
gateway:
enabled: true
routes:
- id: my-service
uri: lb://my-service
predicates:
- Path=/my-service/**
这个带来的问题是每加一个服务,都得手动在配置文件里面加入路由,而且需要重启网关才能生效。
那有没有一种方法既能根据eureka自动配置路由,又能加入context-path呢?
思路
答案当然是有的,首先要知道为啥网关能自动的将/my-service/api/api-test自动的转发到对应服务上的/api/api-test地址,通过负载均衡找到一个服务节点这个暂且不讨论,主要是把/my-service/api/api-test替换成/api/api-test的过程,这个过程其实是在org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory
这个类里面定义的:
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest req = exchange.getRequest();
addOriginalRequestUrl(exchange, req.getURI());
String path = req.getURI().getRawPath();
String newPath = path.replaceAll(config.regexp, replacement);
ServerHttpRequest request = req.mutate().path(newPath).build();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, request.getURI());
return chain.filter(exchange.mutate().request(request).build());
}
那这个类是在哪里初始化的呢?
在这里:org.springframework.cloud.gateway.discovery.GatewayDiscoveryClientAutoConfiguration
public static List<FilterDefinition> initFilters() {
ArrayList<FilterDefinition> definitions = new ArrayList<>();
// add a filter that removes /serviceId by default
FilterDefinition filter = new FilterDefinition();
filter.setName(normalizeFilterFactoryName(RewritePathGatewayFilterFactory.class));
String regex = "'/' + serviceId + '/?(?<remaining>.*)'";
String replacement = "'/${remaining}'";
filter.addArg(REGEXP_KEY, regex);
filter.addArg(REPLACEMENT_KEY, replacement);
definitions.add(filter);
return definitions;
}
所以看到了SpringCloudGateway的自动路由注入了替换URL的一个过滤器,它负责把地址中的serviceId替换掉,而我们加的这个context-path正好就是servcieId,所以只要不启用这个过滤器,就不会替换了。
解决
spring:
application:
name: gateway
cloud:
gateway:
enabled: true
discovery:
locator:
enabled: true
lower-case-service-id: true
filters:
是的,只要加一个filters就可以了,默认是空列表,但是还有一个更好的选择是,加入一些默认的过滤器,比如PreserveHostHeader
,它可以将原始请求的Header信息保留,所以最终的配置如下:
spring:
application:
name: gateway
cloud:
gateway:
enabled: true
discovery:
locator:
enabled: true
lower-case-service-id: true
filters:
- PreserveHostHeader