Spring Cloud Gateway如何捕获下游404响应并修改为501状态码
问题原因
之前的自定义过滤器无法正确捕获下游404状态,核心问题有两点:
- 代码在执行
chain.filter()前就调用exchange.getResponse().getStatusCode()读取状态,此时请求还未转发到下游服务,响应还未生成,拿到的200是响应对象初始化的默认值,不是下游返回的真实状态。 - 从实际返回给app1的响应结构看,下游app2的全局异常处理逻辑已经改写了原生404响应:将HTTP响应状态码强制设置为200,真实的404错误信息被封装到响应体的
code、message字段中,直接读取HTTP响应头的状态码自然无法获取到404。
实现方案
方案1:网关层直接拦截未开发接口(最推荐,无需自定义代码)
当前仅endPoint3、endPoint4两个接口暂未实现,不需要把请求转发到app2再修改状态,可以直接在网关路由配置中拦截这部分路径,直接返回501,性能更高、维护更简单。等后续接口开发完成,把对应路径迁移到已上线的路由规则中即可。
配置示例:
# 已开发完成的接口路由,优先级更高 spring.cloud.gateway.routes[0].id=app2-completed spring.cloud.gateway.routes[0].uri=lb://app2 spring.cloud.gateway.routes[0].predicates[0]=Path=/1/app2/get/endPoint1/**,/1/app2/get/endPoint2/** spring.cloud.gateway.routes[0].filters[0]=StripPrefix=1 # 未开发的接口直接在网关返回501,不转发到下游 spring.cloud.gateway.routes[1].id=app2-unimplemented spring.cloud.gateway.routes[1].uri=no://op spring.cloud.gateway.routes[1].predicates[0]=Path=/1/app2/get/endPoint3/**,/1/app2/get/endPoint4/** spring.cloud.gateway.routes[1].filters[0]=StripPrefix=1 spring.cloud.gateway.routes[1].filters[1]=SetStatus=501
方案2:全局自动捕获下游404转501(适配下游返回真实HTTP 404的场景)
如果需要全局处理所有下游服务返回的原生404响应,自定义全局过滤器时要注意两点:一是过滤器顺序必须在响应写回过滤器之前执行,二是要等下游响应头返回后再读取状态码。
代码示例:
@Component public class NotFoundToNotImplementedFilter implements GlobalFilter, Ordered { @Override public int getOrder() { // 顺序要小于NettyWriteResponseFilter的默认顺序-1,确保在响应写回前修改状态 return -2; } @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpResponse originalResponse = exchange.getResponse(); ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) { @Override public HttpStatusCode getStatusCode() { HttpStatusCode originStatus = super.getStatusCode(); // 下游返回404时统一改成501 if (originStatus == HttpStatus.NOT_FOUND) { return HttpStatus.NOT_IMPLEMENTED; } return originStatus; } }; return chain.filter(exchange.mutate().response(decoratedResponse).build()); } }
方案3:适配下游将404封装为HTTP 200响应体的场景
如果下游服务统一把错误码封装在响应体中、HTTP状态固定返回200(和当前遇到的情况一致),需要在过滤器中解析响应体内容,判断到是404错误时同时修改HTTP状态码和响应内容。
代码示例:
@Component public class NotFoundToNotImplementedFilter implements GlobalFilter, Ordered { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @Override public int getOrder() { return -2; } @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpResponse originalResponse = exchange.getResponse(); DataBufferFactory bufferFactory = originalResponse.bufferFactory(); ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) { @Override public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) { if (body instanceof Flux) { Flux<? extends DataBuffer> fluxBody = Flux.from(body); return super.writeWith(fluxBody.buffer().map(dataBuffers -> { // 拼接完整响应体 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); dataBuffers.forEach(dataBuffer -> { byte[] content = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(content); DataBufferUtils.release(dataBuffer); // 释放缓存避免内存泄漏 try { outputStream.write(content); } catch (IOException e) { throw new RuntimeException(e); } }); byte[] responseBytes = outputStream.toByteArray(); String responseStr = new String(responseBytes, StandardCharsets.UTF_8); try { JsonNode responseJson = OBJECT_MAPPER.readTree(responseStr); // 判断响应体中code为404时修改状态 if (responseJson.has("code") && responseJson.get("code").asInt() == 404) { setStatusCode(HttpStatus.NOT_IMPLEMENTED); // 按需修改响应体内容 ObjectNode mutableJson = (ObjectNode) responseJson; mutableJson.put("code", 501); mutableJson.put("type", "Not Implemented"); responseBytes = OBJECT_MAPPER.writeValueAsBytes(mutableJson); } } catch (Exception e) { // 响应解析失败时直接返回原内容 } return bufferFactory.wrap(responseBytes); })); } return super.writeWith(body); } }; return chain.filter(exchange.mutate().response(decoratedResponse).build()); } }
内容的提问来源于stack exchange,提问作者luuzjnmmemjrwfwpsb
相关产品推荐
相关产品推荐

