如何让 Reactor Netty Server 忽略请求 URL 的尾部斜杠?
可以通过自定义路由匹配条件,让 /foo 和 /foo/ 调用同一个处理器,无需引入 Spring Framework 或重复编写业务逻辑。已核对的 Reactor Netty 1.2.18 HttpServer 和 HttpServerRoutes 公开 API 没有提供忽略尾部斜杠的专用全局配置选项。原问题没有给出依赖版本,其他版本应以对应文档为准。HttpServer API、HttpServerRoutes API
为什么 /foo/ 会返回 404?
问题中注册了 /foo 路由,希望迁移后继续兼容 /foo/,但后者默认返回 404。这表明现有路由配置没有覆盖带尾部斜杠的请求。
/foo 与 /foo/ 可以代表不同资源。Javalin 的兼容行为属于其路由策略,迁移到 Reactor Netty 后,需要自行实现相同的策略。项目没有使用 Spring Framework,调整 Spring 的路径匹配配置无法解决这个问题。
使用一个匹配条件接收两种路径
HttpServerRoutes.route(Predicate, BiFunction) 支持自定义请求匹配条件,可以用一个路由条目接收两种路径。下面以 GET 请求为例:route API
import io.netty.handler.codec.http.HttpMethod;
import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
public class ServerApplication {
public static void main(String[] args) {
DisposableServer server = HttpServer.create()
.port(8080)
.route(routes -> routes.route(
req -> HttpMethod.GET.equals(req.method())
&& matchesPath(req.fullPath(), "/foo"),
(req, res) -> res.sendString(Mono.just("foo"))
))
.bindNow();
server.onDispose().block();
}
// expected 为规范路径:除根路径外,不以 / 结尾。
private static boolean matchesPath(String actual, String expected) {
return expected.equals(actual)
|| (!"/".equals(expected)
&& (expected + "/").equals(actual));
}
}
辅助方法只接受规范路径及其多一个尾部斜杠的形式,不会接受 /foobar 或 /foo//。增加其他固定路径时,可以复用这个方法。
代码使用 fullPath() 获取路径部分。Reactor Netty 的 path() 会移除开头和结尾的 /,不能直接把 req.path() 与 "/foo" 比较。fullPath() 返回解码后的路径,也不能将其当作原始 URI 字符串使用。HttpInfos 源码
这种方案的适用范围
这种方案让两个路径调用同一个处理器,不会修改请求 URI,也不会让已有的所有 routes.get(...) 自动兼容尾部斜杠。如果处理器或其他组件继续读取请求 URI,仍可能看到 /foo/。
如果路由很多,可以把 HTTP 方法、规范路径和处理器的注册过程封装为统一方法,在其中应用上述匹配规则。这样可以集中维护兼容策略,每条业务路由只需声明一次。
对于 /users/{id} 这类参数化路由,不能直接使用字符串相等比较。上面的自定义 Predicate 示例也没有配置路径参数提取。如果现有处理器依赖 req.param("id"),可以给 /users/{id} 和 /users/{id}/ 注册同一个处理器,再通过注册辅助方法减少重复代码。
还要检查路由顺序。默认情况下,Reactor Netty 使用声明顺序中的第一个匹配项;宽泛的匹配条件可能提前接收原本应交给其他处理器的请求。路由匹配顺序说明
迁移时如何验证?
启动示例后,可以执行:
curl -i 'http://localhost:8080/foo'
curl -i 'http://localhost:8080/foo/'
curl -i 'http://localhost:8080/foo/?x=1'
curl -i 'http://localhost:8080/foo//'
curl -i -X POST 'http://localhost:8080/foo/'
按照示例的匹配规则,前三个 GET 请求应进入同一个处理器;/foo// 和 POST 请求不应命中该路由。这是预期行为,需要在实际依赖版本和项目配置下验证。
迁移验证还应覆盖已有的鉴权、缓存和路径参数处理,确保它们对这两种路径采用一致的业务规则。如果需要兼容编码后的路径分隔符,也应单独确定规则,避免把解码后的路径匹配结果等同于原始请求目标。
备注:内容仅供参考。
