实验环境
<spring.cloud-version>2021.0.5</spring.cloud-version>
<spring.cloud.alibaba-version>2021.1</spring.cloud.alibaba-version>
内容中心
# 应用名称
spring:
application:
name: content-center
cloud:
nacos:
discovery:
server-addr: localhost:8848
server:
port: 8081
servlet:
context-path: /content-center
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
用户中心
# 应用名称
spring:
application:
name: user-center
cloud:
nacos:
discovery:
server-addr: localhost:8848
server:
port: 8082
servlet:
context-path: /user-center
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
网关
# 应用名称
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: true
lowerCaseServiceId: true
routes:
- id: content-center
uri: lb://content-center
predicates:
- Path=/content-center/**
- id: user-center
uri: lb://user-center
predicates:
- Path=/user-center/**
server:
port: 9000
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
logging:
level:
org.springframework.cloud: trace
问题描述
当网关配置好时,去调用会发现它莫名其妙的会http-404
,如何解决当前问题呢?请继续往下看,并且会把原理加以说明。
解决方案
要想解决此问题,需要覆盖默认的URL过滤器。只需要在网关yaml配置中添加如下代码即可。那为什么会出现这样的问题呢?请继续往下看
spring.cloud.gateway.discovery.locator.filters:
- StripPrefix=0
造成这种问题的原因
如果需要自己debug验证时请把上方配置注释再进行,为了方便debug观察建议开启org.springframework.cloud: trace
日志。
logging:
level:
org.springframework.cloud: trace
在一次请求调用时它会先经过org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory#apply
@Override
public GatewayFilter apply(Config config) {
// 替换URL的表达式
String replacement = config.replacement.replace("$\\", "$");
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest req = exchange.getRequest();
addOriginalRequestUrl(exchange, req.getURI());
// 原始请求中的path是有context-path路径
String path = req.getURI().getRawPath();
// 新的path是被替换掉的,点击源码查看其实现也是非常简单的
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());
}
@Override
public String toString() {
return filterToStringCreator(RewritePathGatewayFilterFactory.this)
.append(config.getRegexp(), replacement).toString();
}
};
}
org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter
这个类是实际调用微服务的实现。
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
if (url == null || (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
return chain.filter(exchange);
}
// preserve the original url
addOriginalRequestUrl(exchange, url);
if (log.isTraceEnabled()) {
log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName() + " url before: " + url);
}
URI requestUri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
String serviceId = requestUri.getHost();
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
.getSupportedLifecycleProcessors(clientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
RequestDataContext.class, ResponseData.class, ServiceInstance.class);
DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(
new RequestDataContext(new RequestData(exchange.getRequest()), getHint(serviceId)));
LoadBalancerProperties loadBalancerProperties = clientFactory.getProperties(serviceId);
return choose(lbRequest, serviceId, supportedLifecycleProcessors).doOnNext(response -> {
if (!response.hasServer()) {
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
.onComplete(new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, response)));
throw NotFoundException.create(properties.isUse404(), "Unable to find instance for " + url.getHost());
}
ServiceInstance retrievedInstance = response.getServer();
URI uri = exchange.getRequest().getURI();
// if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
// if the loadbalancer doesn't provide one.
String overrideScheme = retrievedInstance.isSecure() ? "https" : "http";
if (schemePrefix != null) {
overrideScheme = url.getScheme();
}
DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(retrievedInstance,
overrideScheme);
URI requestUrl = reconstructURI(serviceInstance, uri);
if (log.isTraceEnabled()) {
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
}
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
exchange.getAttributes().put(GATEWAY_LOADBALANCER_RESPONSE_ATTR, response);
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, response));
}).then(chain.filter(exchange))
.doOnError(throwable -> supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
.onComplete(new CompletionContext<ResponseData, ServiceInstance, RequestDataContext>(
CompletionContext.Status.FAILED, throwable, lbRequest,
exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR)))))
.doOnSuccess(aVoid -> supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
.onComplete(new CompletionContext<ResponseData, ServiceInstance, RequestDataContext>(
CompletionContext.Status.SUCCESS, lbRequest,
exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR), buildResponseData(exchange,
loadBalancerProperties.isUseRawStatusCodeInResponseData())))));
}