Spring Cloud Gateway动态路由与全局过滤器排查
Wucheng

Spring Cloud Gateway 动态路由与全局过滤器问题排查

问题背景

在使用 Spring Cloud Gateway 构建 API 网关时,实现了两个核心 GlobalFilter

  1. **DynamicRouterFilter**:根据请求头中的 REAL_URL 动态转发请求
  2. **CustomGlobalFilter**:实现鉴权、签名校验、限流、调用次数扣减及响应日志记录

但在实际运行中遇到两个关键问题:

  • 请求未正确转发到下游服务
  • 响应体日志未打印(handleResponse 未生效)

原代码实现

DynamicRouterFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Slf4j
@Component
public class DynamicRouterFilter implements GlobalFilter, Ordered {

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
HttpHeaders headers = request.getHeaders();
// 验证请求头
if (!headers.containsKey(REAL_URL)){
return handleError(exchange.getResponse(), HttpStatus.BAD_REQUEST, "无效的请求地址");
}
String realUrl = headers.getFirst(REAL_URL);
if(StringUtils.isBlank(realUrl)){
return handleError(exchange.getResponse(), HttpStatus.BAD_REQUEST, "无效的请求地址");
}

try {
URI uri = getUri(realUrl, request);
log.info("动态路由转发: {}", uri);
// 设置 GATEWAY_REQUEST_URL_ATTR 属性
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, uri);
ServerHttpRequest newRequest = request.mutate()
.uri(uri) // 更改请求的URI
.build();
return chain.filter(exchange.mutate().request(newRequest).build());
} catch (URISyntaxException e) {
return handleError(exchange.getResponse(), HttpStatus.BAD_REQUEST, "无效的请求地址");
}
}
// 重新拼接URL函数
private static URI getUri(String realUrl, ServerHttpRequest request) throws URISyntaxException {}

@Override
public int getOrder() {
return 1;
}
}

CustomGlobalFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@Slf4j
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//打印请求日志
....
ServerHttpResponse response = exchange.getResponse();

// 其他业务逻辑
......
// 当响应后进行日志记录
ServerHttpResponseDecorator decoratedResponse = handleResponse(response);
return chain.filter(exchange.mutate().response(decoratedResponse).build());
}

@Override
public int getOrder() {
return 0;
}

// 忽略其他逻辑,只考虑获取装饰后的response
public ServerHttpResponseDecorator handleResponse(ServerHttpResponse response) {
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(response) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
// 这里才能获取到响应体数据
if (body instanceof Flux) {
Flux<DataBuffer> fluxBody = (Flux<DataBuffer>) body;
return DataBufferUtils.join(fluxBody).flatMap(dataBuffer -> {
try {
// 拼接响应体
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
String responseBody = new String(content, StandardCharsets.UTF_8);
// 日志打印
....
// 重新包装响应体
DataBuffer newBuffer = response.bufferFactory().wrap(content);
DataBufferUtils.release(dataBuffer);
return super.writeWith(Mono.just(newBuffer));
} catch (Exception e) {
log.error("处理响应体时出错", e);
return handleError(response);
}
})
}
return super.writeWith(body);
}
};
return decoratedResponse;
}
}

问题一:请求未转发到下游服务

现象

  • 网关日志显示 DynamicRouterFilter 成功设置了目标 URI(如 http://localhost:8123/api/name/user
  • 但下游目标服务未收到请求
  • 网关最终返回 200404,且 routeUri=no://op

原因

Spring Cloud Gateway 内置的 RouteToRequestUrlFilter(order = 10000)在 DynamicRouterFilter 之后执行,并覆盖了我们手动设置的 GATEWAY_REQUEST_URL_ATTR

即使设置了正确的 URI,RouteToRequestUrlFilter 仍会根据 route 配置中的 uri: no://op 重新生成无效 URL

解决方案

确保 DynamicRouterFilterRouteToRequestUrlFilter 之后执行,从而覆盖其结果:

1
2
3
4
5
6
7
@Component
public class DynamicRouterFilter implements GlobalFilter, Ordered {
@Override
public int getOrder() {
return 20000; // 必须 > 10000 (RouteToRequestUrlFilter 的 order)
}
}

同时,确保 route 配置存在(即使 uri 是占位符):

1
2
3
4
5
6
7
8
spring:
cloud:
gateway:
routes:
- id: dynamic-route
uri: http://placeholder # 合法 URI,避免解析异常
predicates:
- Path=/**

问题二:响应日志未打印

现象

  • CustomGlobalFilter.handleResponse() 中的 log.info("响应体: ...") 未执行

根本原因

1. 响应装饰器未生效

NettyWriteResponseFilter(order = -1)在 CustomGlobalFilter(order = 0)之前执行,提前锁定了响应流,在之后包装的 ServerHttpResponseDecorator 被忽略


解决方案

1. 调整 Filter 执行顺序

CustomGlobalFilterNettyWriteResponseFilter 之前执行:

1
2
3
4
@Override
public int getOrder() {
return -2; // 必须 < -1 (NettyWriteResponseFilter 的 order)
}