Spring Cloud Gateway 404排查:context-path与路由转发路径对齐实战

发布时间:2026/10/13 21:37:46
Spring Cloud Gateway 404排查:context-path与路由转发路径对齐实战
1. 从一次线上404说起context-path与网关路由的碰撞事情发生在某个再普通不过的周三下午。我们团队维护的一套微服务系统刚完成一轮迭代把其中一个核心业务服务从单体拆成了独立服务部署到测试环境后前端同学反馈所有经过网关访问这个服务的接口全部返回404但绕过网关直接访问服务实例的端口接口又是通的。这个现象非常典型——直连能通、过网关就404几乎可以断定问题出在网关的转发环节而不是业务服务本身。我们这套系统用的是 Spring Cloud Gateway 做统一入口服务注册发现走的是注册中心路由配置一部分是静态的 YAML一部分是动态下发的。出问题的这个服务恰好是唯一一个在application.yml里配置了server.servlet.context-path的服务。排查过程不算复杂但背后牵扯到的知识点却相当密集网关的断言Predicate怎么匹配路径、StripPrefix过滤器怎么裁剪前缀、context-path在服务端是怎么参与路径解析的、注册中心里注册的到底是哪个路径。这几个环节只要有一个对不上404 就会如约而至。我把这次排查的完整链路、每个环节的判断依据、以及最后落地的几种修复方案整理出来希望能帮到正在被同类问题折磨的同学。这篇文章适合三类人看一是刚接触 Spring Cloud Gateway、对路由匹配机制还不太熟的同学二是正在做服务拆分、被路径问题卡住的开发者三是想系统梳理网关路径转发链路的架构同学。我会尽量把每个为什么讲透而不是只丢一个结论让你抄。2. 先搞清楚404到底是谁返回的遇到404第一反应不应该是改配置而是先确认这个404是谁吐出来的。是网关自己没匹配到路由返回的还是网关匹配到了路由、转发给下游服务后下游服务返回的这两种情况的排查方向完全不同。2.1 网关层404与下游404的区分方法最直接的办法是看响应体和响应头。Spring Cloud Gateway 在找不到匹配路由时默认返回的是一个比较干净的404响应体通常为空或者只有极简内容而且不会带有下游服务特有的响应头。而下游服务返回的404往往会带上该服务自己的错误结构比如统一的 JSON 错误码、Content-Type是application/json、甚至带有服务名标识。另一个更可靠的手段是打开网关的日志。把org.springframework.cloud.gateway这个包的日志级别调到DEBUG你会看到类似Route matched或者No RoutePredicateHandlerMapping found这样的关键信息。如果日志里压根没有路由匹配成功的记录那就是网关层没匹配上如果匹配上了、转发了但下游返回404那问题就在下游的路径解析上。我们当时的情况是网关日志显示路由匹配成功了请求也确实转发到了下游服务但下游返回了404。这就把问题范围一下子缩小到了转发过去的路径和下游期望的路径不一致上。2.2 用直连对比法快速定位我习惯用一个笨但极其有效的办法拿到网关转发时实际使用的目标 URL然后手动用这个 URL 去直连下游服务。怎么拿到实际转发的 URL在网关日志里DEBUG级别会打印出转发目标地址或者临时加一个全局过滤器把ServerWebExchange里的GATEWAY_REQUEST_URL_ATTR打出来。假设网关配置的路由是/api/order/**转发到order-service而order-service配置了context-path: /order。那么网关转发过去的路径可能是/api/order/list但下游服务实际期望的是/order/api/order/list或者/order/list——具体是哪个取决于StripPrefix有没有配、配了几位。直连测试时你分别拼出这几种路径去请求哪个返回200哪个就是下游真正期望的路径。这一步做完问题基本就水落石出了。提示直连测试时记得带上和网关转发时一致的请求头尤其是Host和Content-Type有些服务对Host有校验会干扰判断。3. context-path在服务端到底做了什么很多同学对context-path的理解停留在给接口加个前缀但它在 Servlet 容器层面到底怎么参与路径解析其实直接决定了网关该怎么配。这里必须把原理讲清楚否则你只是记住了要加个 StripPrefix换个场景又会懵。3.1 Servlet容器中的contextPath与servletPath在 Servlet 规范里一个请求的路径被拆成三部分contextPath、servletPath、pathInfo。假设请求是/order/api/list而应用配置了context-path: /order那么容器解析结果是contextPath /orderservletPath /api/list在 Spring MVC 里通常整体作为映射路径处理pathInfo一般为空。关键点在于Spring MVC 的DispatcherServlet在做 HandlerMapping 时用的是去掉contextPath之后的路径。也就是说你的 Controller 上写RequestMapping(/api/list)它匹配的是/api/list而不是/order/api/list。context-path是容器层面的前缀不参与 Controller 的映射匹配。这就解释了一个常见困惑为什么我配了context-pathController 里却不用写这个前缀因为容器已经帮你剥掉了。3.2 网关转发时路径是怎么拼的网关这边路由配置里的uri指向的是服务实例地址比如lb://order-service。当请求/api/order/list进来网关匹配到路由后默认会把完整的原始路径转发给下游除非你配置了StripPrefix之类的过滤器去裁剪。所以默认情况下下游收到的是/api/order/list。但下游服务配了context-path: /order容器会认为contextPath应该是/order可实际请求路径开头是/api对不上。这时候容器会怎么处理它会认为这个请求不属于本应用的 context直接返回404。这就是我们那次问题的根因——网关转发的路径没有包含下游的 context-path 前缀导致下游容器拒绝处理。3.3 一个容易混淆的等价关系这里有个等价关系必须记住下游服务配了context-path: /order等价于所有接口的访问路径前面都要加/order。网关要做的就是保证转发过去的路径以/order开头。有两种达成方式一是路由的Path断言本身就带/order前缀转发时原样带过去二是断言不带但通过RewritePath或SetPath过滤器在转发前把前缀补上。选哪种取决于你的路由规划风格后面会详细对比。4. 路由断言、StripPrefix与context-path的三方博弈把这三个东西放在一起看很多404的成因就一目了然了。它们之间的关系可以用一句话概括断言决定什么样的请求进来StripPrefix 决定转发前砍掉几段context-path 决定下游要求路径以什么开头。三者必须自洽。4.1 StripPrefix的工作原理与计数陷阱StripPrefix1的意思是把请求路径按/分割后砍掉最前面的1段。注意是段不是字符。/api/order/list按/分割后是[, api, order, list]开头的空串不算有效段StripPrefix1砍掉api剩下/order/list。计数陷阱在于很多人以为StripPrefix1是砍掉第一个斜杠前的内容其实它砍的是第一个有效路径段。如果你的断言是/api/**那StripPrefix1正好砍掉api但如果断言是/api/v1/**你想砍掉api/v1两段就得写StripPrefix2。数错段数转发路径就会多一段或少一段404 随之而来。4.2 三种典型配置组合的对照我把常见的配置组合和结果整理成表方便你对照自己的场景断言 PathStripPrefix下游 context-path下游收到路径结果/api/order/**0不配/order/api/order/list404前缀不对/api/order/**1/order/order/list200正确/api/order/**2/order/list404砍多了/order/**0/order/order/list200正确/api/**1/order/order/list若原路径为/api/order/list200正确看这张表你会发现核心不是 StripPrefix 配几而是转发后的路径是否以 context-path 开头。只要满足这个条件怎么配都行。理解了这一点你就不会再死记context-path 要配 StripPrefix1这种口诀了。4.3 为什么直连能通会误导人直连测试时你手动输入的路径往往已经带了/order前缀因为你从服务日志或接口文档里看到的就是这个所以能通。但网关转发时用的是另一套路径拼接逻辑两者不一致就出现了直连通、过网关404的诡异现象。这不是矛盾而是你测试时用的路径和网关实际转发的路径根本不是一个。排查时一定要以网关实际转发的 URL为准而不是以你脑子里以为的 URL 为准。5. 完整排查链路从404到定位根因这一节我把当时的排查步骤完整还原你可以照着这个顺序走一遍基本能覆盖90%以上的同类问题。5.1 第一步确认404来源打开网关 DEBUG 日志搜索路由匹配记录。如果没匹配上检查断言的Path是否写对、路由是否真的加载了动态路由的话检查配置中心有没有下发成功。如果匹配上了进入下一步。5.2 第二步打印实际转发URL临时加一个全局过滤器优先级设低一点Ordered.LOWEST_PRECEDENCE在filter方法里打印exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)。这个属性里就是网关最终要请求的完整 URL。把它记下来。5.3 第三步直连验证下游期望路径用上一步拿到的 URL 的 path 部分直连下游服务实例。如果404说明下游期望的路径和这个不一样。这时候去看下游服务的context-path配置以及 Controller 的实际映射路径反推出下游真正期望的完整路径。5.4 第四步反推网关配置该怎么改对比网关实际转发路径和下游期望路径差异在哪就改哪。如果是少了 context-path 前缀要么给断言加上这个前缀要么用RewritePath补上如果是多了前缀用StripPrefix砍掉。改完重启网关再走一遍验证。5.5 第五步回归测试所有相关路由改完一个服务的路由后别忘了检查同一网关下其他服务的路由有没有受影响。尤其是当你调整了全局过滤器或公共路由配置时很容易误伤。我们当时就因为改了一个StripPrefix的默认值导致另一个服务的路径被多砍了一段又排查了半小时。注意动态路由场景下改完配置后要确认配置中心推送成功、网关确实刷新了路由。有些团队的路由刷新有缓存或延迟改完立刻测可能还是旧配置白白浪费时间。6. 几种修复方案的取舍与落地定位到根因后修复方案不止一种。选哪种取决于你的路由规划风格和团队约定。我把几种方案摆出来说说各自的适用场景和坑。6.1 方案一断言带context-path前缀不裁剪这是最直观的方案。路由断言直接写成/order/**转发时不裁剪下游收到/order/list正好匹配 context-path。优点是配置简单、路径一目了然缺点是网关对外暴露的路径带上了服务内部的前缀如果这个前缀是内部命名、对外不友好就不太合适。另外如果多个服务都想用统一的对外前缀比如都走/api这个方案就不好统一了。6.2 方案二断言用对外前缀StripPrefix裁剪后补context-path这是最常见的方案。断言写/api/order/**StripPrefix1砍掉api剩下/order/list正好匹配。但如果对外前缀有多段比如/api/v1/order/**就要StripPrefix2。这个方案的坑在于段数容易数错而且一旦对外前缀调整所有相关路由的 StripPrefix 都要跟着改维护成本偏高。6.3 方案三用RewritePath精确重写RewritePath用正则做路径重写最灵活也最精确。比如filters: - RewritePath/api/order/(?segment.*), /order/$\{segment}这个配置把/api/order/xxx重写成/order/xxx不管后面跟什么都能正确拼接。优点是意图明确、不受段数影响缺点是要写正则对不熟悉正则的同学有一定门槛而且正则写错会导致更难排查的问题。6.4 方案四统一在网关层剥离context-path如果团队约定所有服务都不配context-path把前缀统一交给网关管理那问题从根上就不存在了。这是最干净的方案但需要推动所有服务改造存量系统迁移成本高。新项目建议优先考虑这种约定能省掉大量路径对齐的麻烦。我个人在项目里的选择是新服务一律不配 context-path对外路径由网关统一规划存量服务如果已经配了用 RewritePath 精确重写而不是用 StripPrefix 数段数。这样既避免了段数陷阱也让路径映射关系在配置里一眼可见。7. 几个容易忽略的细节与踩坑记录排查完这次问题后我又陆续遇到过几个相关的坑一并记下来都是文档里不太会写、但实际很常见的。7.1 注册中心里注册的路径带不带context-path有些注册中心的健康检查或服务元数据里会带上 context-path有些不会。如果你的网关是基于服务实例的元数据来拼路径的比如自定义了负载均衡策略那注册的路径信息就会影响转发结果。我们有一次就是因为服务注册时上报的路径带了 context-path而网关又额外补了一次导致路径里出现了两个/order直接404。排查时记得看一眼注册中心里这个服务的元数据。7.2 大小写与末尾斜杠的隐形差异/Order和/order在默认配置下是不等价的/order/list和/order/list/也可能被下游区别对待。网关转发时如果路径末尾多了或少了一个斜杠下游的路径匹配就可能失败。这类问题在日志里看起来路径明明一样但就是404非常折磨人。建议在网关层加一个规范化过滤器统一处理末尾斜杠和大小写。7.3 全局过滤器顺序对路径的影响如果你同时用了StripPrefix和RewritePath它们的执行顺序会影响最终路径。过滤器是按Order排序执行的顺序不对可能先重写再裁剪结果完全不是你想要的。排查时把相关过滤器的Order值打出来确认执行顺序符合预期。7.4 本地能通、测试环境不通的环境差异本地开发时很多人图省事不配 context-path或者用不同的路由配置导致本地一切正常一上测试环境就404。这类问题的根源是环境间配置不一致。建议把路由配置纳入版本管理各环境用同一份基础配置只覆盖必要的差异项减少这类环境玄学。8. 把路径对齐这件事做成规范踩过几次坑之后我在团队里推了一套简单的路径规范执行下来同类问题基本绝迹了。核心就三条第一对外路径统一由网关规划服务内部不感知对外前缀第二新服务不配 context-path如果非要配必须在服务文档里显式标注并在网关路由配置里加注释说明第三所有涉及路径裁剪或重写的路由必须写清楚转发前路径和转发后路径的对照注释方便后来人一眼看懂。这三条看起来简单但真正落地后路径相关的404从每月好几次降到了几乎为零。路径问题本质上不是技术难题而是约定和沟通问题——只要转发前后的路径关系在配置里写得明明白白谁来看都不会懵。最后分享一个我常用的小技巧在网关里加一个只在测试环境生效的调试端点输入任意路径返回它会匹配到哪条路由、最终转发成什么 URL。这个端点帮我们省下了大量改配置-重启-测试的循环时间定位路径问题的效率提升非常明显。如果你也在维护网关强烈建议做一个类似的工具投入很小回报很大。