0
点赞
收藏
分享

微信扫一扫

Spring Cloud Gateway context-path与注册中心名称一致转发失效问题

实验环境

<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​​,如何解决当前问题呢?请继续往下看,并且会把原理加以说明。

Spring Cloud Gateway context-path与注册中心名称一致转发失效问题_java

解决方案

要想解决此问题,需要覆盖默认的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())))));
}



举报

相关推荐

0 条评论