Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查 - 教程

news/2025/9/18 12:58:08/文章来源:https://www.cnblogs.com/yfceshi/p/19098547

Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查

最近做网关 Spring Cloud Gateway功能开发,联调的时候,把本地网关代理出去了,遇到了一个令人困惑的问题:为什么同一个接口,用 IP 形式访问就正常,而换成域名就直接返回 404 Not Found

这个问题抽象出来是这样的:

本地用 curl 模拟调用,无论是 IP 还是域名,都能正常转发。按理来说域名只是加了个代理转发,网关能够收到域名过来的请求,就正常的。这次分析了 Spring Cloud Gateway 的源码,一探究竟。


源码追踪:追查 404 的元凶

我的追查从 Spring WebFlux 的核心分发器 DispatcherHandler 开始。

DispatcherHandler#handle:请求分发入口

DispatcherHandler 负责将传入的 ServerWebExchange(代表一个 HTTP 请求)分发给正确的处理器(Handler)。

public Mono handle(ServerWebExchange exchange) {
if (this.handlerMappings == null) {
return createNotFoundError();
}
if (CorsUtils.isPreFlightRequest(exchange.getRequest())) {
return handlePreFlight(exchange);
}
return Flux.fromIterable(this.handlerMappings)
.concatMap(mapping -> mapping.getHandler(exchange))
.next()
.switchIfEmpty(createNotFoundError())
.flatMap(handler -> invokeHandler(exchange, handler))
.flatMap(result -> handleResult(exchange, result));
}

在这里插入图片描述

这段代码采用了响应式编程的链式调用,依次执行逻辑:

查找 Handler:Flux.fromIterable(this.handlerMappings)将所有注册的**HandlerMapping**(如 RoutePredicateHandlerMappingSimpleUrlHandlerMapping 等)放入一个响应式流中。concatMap会顺序地调用每个mappinggetHandler()方法,尝试找到能够处理该请求的Handler。

接下来,我将目光锁定在了 Spring Cloud Gateway 自己的 Handler 上。

RoutePredicateHandlerMapping#getHandlerInternal:路由匹配核心

作为 Gateway 路由匹配的核心,RoutePredicateHandlerMapping 的主要任务就是根据配置的路由谓词,为请求找到对应的 Route

RoutePredicateHandlerMapping实现了getHadnlerInternal方法,整体是一个模版设计模式,某些细节下放到子类实现。

protected Mono getHandlerInternal(ServerWebExchange exchange) {
// ...
return lookupRoute(exchange)
.flatMap((Function>) r -> {
// ... 找到路由后返回 FilteringWebHandler
return Mono.just(webHandler);
}).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]");
})));
}
protected Mono<
Route> lookupRoute(ServerWebExchange exchange) {
return this.routeLocator.getRoutes()
// individually filter routes so that filterWhen error delaying is not a
// problem
.concatMap(route ->
Mono.just(route).filterWhen(r ->
{
// add the current route we are testing
exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
return r.getPredicate().apply(exchange);
})
// instead of immediately stopping main flux due to error, log and
// swallow it
.doOnError(e -> logger.error("Error applying predicate for route: " + route.getId(), e))
.onErrorResume(e ->
Mono.empty()))
// .defaultIfEmpty() put a static Route not found
// or .switchIfEmpty()
// .switchIfEmpty(Mono.<Route>empty().log("noroute")).next()// TODO: error handling.map(route ->{if (logger.isDebugEnabled()) {logger.debug("Route matched: " + route.getId());}validateRoute(route, exchange);return route;});/** TODO: trace logging if (logger.isTraceEnabled()) {* logger.trace("RouteDefinition did not match: " + routeDefinition.getId()); }*/}

源码解读:

  • lookupRoute(exchange):这是查找路由的核心方法。它会遍历所有已定义的路由,并用每个路由的谓词去判断是否与当前请求匹配。
  • .flatMap(...): 如果 lookupRoute 找到了 Route,就会返回一个 Mono.just(webHandler),这里的 webHandler 就是 FilteringWebHandler,用于执行路由过滤器链。
  • .switchIfEmpty(...): 如果 lookupRoute 没有找到任何匹配的路由,它会返回一个空的 Mono,从而导致 getHandlerInternal 方法也返回 Mono.empty(),即它无法处理该请求。

RoutePredicateHandlerMapping 因为没有匹配到任何路由,无法返回有效的 FilteringWebHandler
接下来,我们看遍历route,然后执行谓词匹配

GatewayPredicate#test

我继续追踪,最终定位到了路由谓词判断的核心方法:org.springframework.cloud.gateway.handler.predicate.GatewayPredicate#test

Java

public boolean test(ServerWebExchange exchange) {
PathContainer path = parsePath(exchange.getRequest().getURI().getRawPath());
PathPattern match = null;
for (PathPattern pathPattern : pathPatterns) {
if (pathPattern.matches(path)) {
match = pathPattern;
break;
}
}
// ...
return match != null;
}

源码解读:

  • exchange.getRequest().getURI().getRawPath(): 谜底就在这里! 这行代码用于获取请求的 URI 路径。getRawPath() 方法获取的是未解码的、原始的路径字符串,这对于路径匹配至关重要。
  • pathPattern.matches(path): 这里就是进行路由匹配的最终判断,将从 getRawPath() 获取的路径与路由规则进行对比。

核心问题就出在 getRawPath() 的返回值上。

IP 形式的请求:http://localhost:9900/api-user/xxxx1

getRawPath() 得到 /api-user/xxxx1。该路径与我们配置的路由规则完美匹配,test 方法返回 true,路由成功。

域名形式的请求:http://xxx.xxx.xxx/:9900/api-user/xxxx1

这里的 URL 格式不规范,在主机名和端口号之间多了一个斜杠 /。根据 URI 规范,端口号后面不应有斜杠。当 Java 的 URI 解析器遇到这个不规范的格式时,它会将 :9900/ 当作路径的一部分,而端口视为80,

因此,getRawPath() 返回 /:9900/api-user/xxxx1。看到这个变量,长的非常奇怪,一下子问题点就发现了,这个不正确的路径自然无法匹配 /api-user/** 的路由规则以及任意其他的路由规则,test 方法返回 false,路由失败。
无法走网关的匹配逻辑,就无法使用RoutePredicateHandlerMapping的FilteringWebHandler
后续流程交给SimpleUrlHandlerMapping的ResourceWebhandler的逻辑,同样的在ResourceWebHandler里面也找不到:9900/api-user/xxxx1路径。
这个ResourceWebHandler是在

  • META-INF/resources/
  • resources/
  • static/
  • public/

这些地方找静态资源,导致无法找到资源,从而404。接口响应的404也是在这里返回的。

在这里插入图片描述


总结:问题的完整链路

  1. URL 格式不规范: 域名请求 http://xxx.xxx.xxx/:9900/ 多了一个不该存在的斜杠。
  2. 路径解析错误:getRawPath():9900/ 解析成了路径的一部分,导致路径变为 /:9900/api-user/xxxx1
  3. 路由匹配失败: 错误的路径无法匹配任何路由规则,GatewayPredicate#test 返回 false
  4. Handler 寻找失败:RoutePredicateHandlerMapping 因为没有找到匹配的路由,所以无法返回 FilteringWebHandler
  5. 请求被 ResourceWebHandler 接管: 鉴于请求没有找到任何业务 Handler,Spring Boot 默认配置中的 SimpleUrlHandlerMapping 会将请求交给 ResourceWebHandler 处理,期望它能找到静态资源。
  6. 最终返回 404: 然而,在静态资源目录下,自然找不到 /:9900/api-user/xxxx1 这个文件。ResourceWebHandler 同样无法处理该请求,最终导致 DispatcherHandler 抛出 404 Not Found 错误。

最终结论与解决方案

问题的根源在于域名 URL 格式不规范,导致网关获取到错误的请求路径,进而无法匹配任何路由,最终返回 404 Not Found 错误。

这次排查不仅解决了实际问题,也让我深刻理解了 URI 规范的重要性,以及 Spring Cloud Gateway 路由匹配的底层机制。在未来,遇到类似的路由问题时,我们应该首先检查请求的原始路径是否符合预期。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/907128.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【光照】[漫反射diffuse]以UnityURP为例

【从UnityURP开始探索游戏渲染】专栏-直达漫反射基本流程 漫反射遵循兰伯特定律(Lamberts Cosine Law),其核心流程如下:‌法线准备‌:获取表面法线向量(通常来自顶点法线或法线贴图) ‌光源方向计算‌:确定光源到表…

OI线下比赛注意事项

这篇写挺好 https://www.cnblogs.com/dengstar/p/17069134.html

3D影像地形图的制作:利用ArcGISPro - 指南

3D影像地形图的制作:利用ArcGISPro - 指南2025-09-18 12:52 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: blo…

n8n实践-使用n8n搭建一个定时发送天气预报邮件的工作流

前言传统的定时任务,一般是使用linux cron定时运行某个位置的某个脚本。需要准备各种运行环境; 脚本比较分散,管理起来相对麻烦; 需要有一定的代码能力,比如常见的shell、python等等; 不方便接入ai, 更换ai也比较…

Cython-编程学习指南第二版-全-

Cython 编程学习指南第二版(全)原文:zh.annas-archive.org/md5/0bc691743f26fcdcabcb6840b706a834 译者:飞龙 协议:CC BY-NC-SA 4.0前言 Cython 是一个工具,它使得编写 Python 的原生扩展变得和编写 Python 代码…

印度尼西亚股票数据API对接实现

环境准备 首先安装必要的依赖包: pip install requests websocket-client pandas numpy基础配置 import requests import json import websocket import threading import time from datetime import datetime# API配…

OpenBMB 发布无分词器 TTS VoxCPM;儿童口语硬件 Dex 融资 480 万美元:拍摄真实物体,对话学习外语丨日报

开发者朋友们大家好:这里是 「RTE 开发者日报」,每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE(Real-Time Engagement) 领域内「有话题的技术」、「有亮点的产品」、「有思考的文章」、「有态度…

一天一款实用的AI工具,第1期,AI标题生成工具

本期介绍的是一款专业的标题生成工具,它能帮你产出高质量标题,让点击率提升,让内容被看见。现实问题 在内容创作的世界里,有句话特别扎心: 好的标题=成功的一半。 很多创作者都遇到过这样的困境: 花了一下午写好…

重组蛋白表达避坑指南

重组蛋白表达避坑指南重组蛋白表达是分子生物学、生物技术以及生物医学研究中非常基础却经常“出问题”的环节。一个合适的蛋白表达方案,不仅要能产生足够的产量,还要确保蛋白正确折叠、具有功能、具有良好的纯度与稳…

易被忽略的vim中视图模式

常见的都是vim三种模式,但视图模式也不可忽略,主要进行批量操作在 Vim 中,可视模式(Visual Mode)是一种强大的文本选择和编辑模式,允许你高亮选中一段文本,然后对其进行操作(如复制、删除、替换、注释等)。 一…

详细介绍:智慧校园统一身份认证中心:一个账号畅行校园内外

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

电商核心业务 - 指南

电商核心业务 - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "…

一言

一些日常的感想,为了节约时间,为了不暴露太多东西,为了不挑起矛盾,内容会很简洁,在合适的时候公布详情。9.17 说好的向阳而生呢?冷静啊,兄弟。 9.18 你们不相信我,我必将证明我,夺回属于我的荣耀。

ai

https://qsqs.life/login?redirect=/system/dashboard本文来自博客园,作者:zjxgdq,转载请注明原文链接:https://www.cnblogs.com/zjxzhj/p/19098509

LlamaIndex 项目深度技术分析 - 详解

LlamaIndex 项目深度技术分析 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monac…

苏州才是最美的烟雨江南,苏州游玩必去的10大景点

苏州才是最美的烟雨江南,苏州游玩必去的10大景点 蜘蛛指南 关注2024-05-22 16:22 北京 来源:澎湃新闻澎湃号湃客 字号苏州人间天堂 最美的烟雨江南 苏州,一个极具江南风情的城市,既有园林之美,也有诗情画意,也是…

深入解析:css消除图片下的白边

深入解析:css消除图片下的白边pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco&quo…

linux增加网卡ip地址

linux增加网卡ip地址example ip addr add 192.168.5.124/24 dev eth0 label eth0:5 ifconfig eth0:5 up ip addr del 192.168.1.100/24 dev eth0 example ip addr add 192.168.10.199/24 dev eth0 label eth0:10 route…

Python 包与环境管理简史:从混乱到优雅

自动包管理工具的先驱:easy_install 在一切规范化工具出现之前,Python 的包管理是相当原始的。开发者们需要把第三方库的源码下载下来,手动放到项目目录里。 为了解决自动安装包的问题,easy_install 应运而生。 20…

qoj853 Flat Organization

SOLUTION FROM WUMIN4 题意 给出一个 \(n\) 个点的带权竞赛图(定向完全图),你可以进行任意次操作,每次操作反转一条边,代价为边权,求使得图强连通的最小代价和与方案,或输出无解。 \(n\le 2000\)。 思路 我们先…