wastnet 把 Java Web 服务的关键链路掌握在自己手中:网络层基于 JDK 原生 NIO 和 Reactor 多路复用模型,不依赖 Netty、Tomcat 等第三方网络库;HTTP/2 的 HPACK、Huffman、ALPN 等组件也由框架自行实现。对业务开发者来说,这些底层能力最终都会汇聚到一个高频入口:HTTP 路由分发。
路由代码看起来只是“路径对应处理函数”,但真正上线后,还要处理静态路径、参数提取、匹配优先级、方法约束、异常响应,以及 h2、h2c 与 HTTP/1.1 的一致性。下面从工程角度拆解这些问题。
路由层应该负责什么
一条请求进入服务器后,路由层通常需要根据 HTTP 方法和规范化后的路径选择处理器。例如:
GET /users -> 查询用户列表
GET /users/:id -> 查询单个用户
POST /users -> 创建用户
DELETE /users/:id -> 删除用户
路由层适合承担以下职责:
- 按 HTTP 方法和路径定位处理器。
- 提取路径参数,例如
/users/42中的id=42。 - 在找不到路径时返回
404 Not Found。 - 在路径存在但方法不匹配时返回
405 Method Not Allowed。 - 将处理器异常转换为稳定的 HTTP 错误响应。
身份认证、数据库事务和复杂业务校验则应放在中间件或业务服务中。否则路由表会迅速变成难以测试的大型条件分支。
还要特别注意匹配顺序。对于下面两条规则:
GET /users/me
GET /users/:id
静态路径 /users/me 应当比参数路径优先。如果路由器只按注册顺序或简单通配规则匹配,me 可能被错误解释为用户 ID。一个稳妥策略是按照“静态段数量更多、参数段数量更少、路径更长”的顺序计算优先级,并在应用启动时检查完全重复的路由。
用一个零依赖示例理解路径参数
来源摘要没有给出 wastnet 的完整路由 API,因此下面不是对其真实接口的复刻,而是一个可以直接运行的 JDK 示例,用来验证路由匹配、参数提取和静态路径优先级。创建 RouteDemo.java:
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class RouteDemo {
record Route(String method, String pattern, String handler) {
int staticSegments() {
int count = 0;
for (String segment : segments(pattern)) {
if (!segment.startsWith(":")) count++;
}
return count;
}
}
record Match(String handler, Map<String, String> params) {}
static final List<Route> routes = new ArrayList<>();
static void add(String method, String pattern, String handler) {
routes.add(new Route(method, normalize(pattern), handler));
routes.sort(Comparator
.comparingInt(Route::staticSegments).reversed()
.thenComparingInt(r -> segments(r.pattern()).size()).reversed());
}
static Match match(String method, String path) {
List<String> actual = segments(normalize(path));
for (Route route : routes) {
if (!route.method().equalsIgnoreCase(method)) continue;
List<String> expected = segments(route.pattern());
if (expected.size() != actual.size()) continue;
Map<String, String> params = new LinkedHashMap<>();
boolean matched = true;
for (int i = 0; i < expected.size(); i++) {
String rule = expected.get(i);
String value = actual.get(i);
if (rule.startsWith(":")) {
params.put(rule.substring(1), value);
} else if (!rule.equals(value)) {
matched = false;
break;
}
}
if (matched) return new Match(route.handler(), params);
}
return null;
}
static String normalize(String path) {
String value = path.split("\\?", 2)[0];
if (value.length() > 1 && value.endsWith("/")) {
value = value.substring(0, value.length() - 1);
}
return value.startsWith("/") ? value : "/" + value;
}
static List<String> segments(String path) {
if (path.equals("/")) return List.of();
return List.of(path.substring(1).split("/"));
}
public static void main(String[] args) {
add("GET", "/users/:id", "getUser");
add("GET", "/users/me", "getCurrentUser");
add("DELETE", "/users/:id", "deleteUser");
for (String[] request : List.of(
new String[]{"GET", "/users/me"},
new String[]{"GET", "/users/42?verbose=true"},
new String[]{"DELETE", "/users/42"})) {
System.out.printf("%s %s -> %s%n",
request[0], request[1], match(request[0], request[1]));
}
}
}
使用 JDK 17 或更高版本编译运行:
javac RouteDemo.java
java RouteDemo
预期可以看到 /users/me 命中静态处理器,而 /users/42 提取出 id=42。接入 wastnet 时,可以保留这里的测试思路,将 add、match 和 handler 替换为项目实际提供的路由注册、请求上下文与响应接口。
这个示例有意保持简单,没有实现 URL 解码、通配符、矩阵参数和冲突检测。生产路由器必须明确这些行为,尤其不能在路径规范化时把 %2F 随意解码成 /,否则可能改变路径边界,甚至造成鉴权绕过。
同一套路由要跨协议验证
wastnet 的特色之一是自研 HTTP/2 协议栈。路由处理器不应该依赖请求来自 HTTP/1.1、TLS 上的 h2,还是明文 h2c;协议解析层应把它们转换成一致的请求语义,再交给路由层。
假设服务监听 8080 提供 h2c,并在 8443 提供启用 ALPN 的 TLS,可以这样实践协议回归测试。请按实际端口、证书和路径修改命令:
# HTTP/1.1
curl --http1.1 -i http://127.0.0.1:8080/users/42
# h2c prior knowledge:客户端直接发送 HTTP/2 连接前言
curl --http2-prior-knowledge -i http://127.0.0.1:8080/users/42
# TLS + ALPN 协商 h2;开发环境使用自签名证书时才加 -k
curl --http2 -k -i https://127.0.0.1:8443/users/42
三次请求应得到一致的状态码、业务响应体和关键响应头。排查问题时,可以增加 -v,观察 ALPN 是否协商出 h2,以及 h2c 客户端是否真的发送了 HTTP/2 请求。
协议一致性测试还应覆盖:
- 查询参数和重复请求头的解析结果。
- 大请求体、空请求体和分块到达的数据。
- 不存在的路由以及方法不匹配的路由。
- 处理器抛出异常后的状态码和连接行为。
- HTTP/2 多路复用下并发请求是否发生上下文串扰。
性能不只取决于底层 NIO
来源摘要提到 wastnet 的基准吞吐可对标 Undertow,但业务项目不能直接把框架基准当成自身容量结论。路由层仍可能引入明显开销,例如每次请求编译正则表达式、遍历整张路由表、创建大量临时字符串,或者在 Reactor 线程中执行阻塞数据库查询。
可以重点检查以下边界:
- 路由规则在启动阶段编译并建立索引,不在请求阶段重复构造。
- 参数容器和请求上下文避免无意义复制,但不能跨 HTTP/2 stream 共享可变状态。
- 阻塞数据库、文件和远程 RPC 调用离开 I/O 事件循环,进入受限工作线程池。
- 压测同时记录延迟分位数、错误率、CPU、内存和 GC,而不是只看吞吐量。
- HTTP/1.1 与 HTTP/2 分别压测,避免协议差异掩盖路由实现的问题。
上线前的路由检查表
采用 wastnet 路由组件时,先用少量接口验证真实 API 和运行模型,再逐步迁移业务。上线前至少确认:
- 静态、参数和通配路由的优先级有确定规则。
- 重复路由在启动时失败,而不是运行时随机覆盖。
404、405、参数错误和内部异常具有统一响应格式。- 路径参数经过 URL 解码、长度限制和业务校验。
- 路由处理器不会阻塞 Reactor I/O 线程。
- HTTP/1.1、h2c 和 TLS h2 使用同一组契约测试。
- 性能结论来自接近真实业务的数据、连接数和处理器逻辑。
自研网络栈让 wastnet 能够精确控制从 NIO 事件循环到 HTTP/2 编解码的完整链路,也意味着使用者需要认真验证协议边界与线程模型。把路由当作可测试的请求分发层,而不是一组散落的回调,才能真正发挥这类零依赖服务器的价值。