前端静态资源和后端 API 分别监听不同端口,是开发阶段常见的部署方式:前端可能运行在 3000,Java API 运行在 8080。但到了测试或生产环境,用户通常只希望访问一个域名和一个端口。wastnet 的反向代理能力可以把这两类请求统一接入,再按路径转发到不同的后端服务。
wastnet 是一款零依赖、自研的 Java Web 服务器,核心基于 JDK 原生 NIO 构建 Reactor 多路复用模型,不依赖 Netty、Tomcat 等第三方网络库。它还自研了 HTTP/2 的 HPACK、Huffman 和 ALPN 协议栈,支持 h2 与 h2c。本文重点放在实际部署模型:如何让一个监听端口同时提供前端页面和后端 API。
统一入口的请求模型
可以把请求分成两类:
/api/*:转发到后端 Java 服务,例如127.0.0.1:8080- 其他路径:由 wastnet 直接读取前端构建目录,例如
/srv/www/app
请求路径经过统一入口后,浏览器不再需要感知后端端口:
浏览器
|
| http://example.com:8080
v
wastnet
|-- /api/users -> http://127.0.0.1:9000/users
|-- /api/login -> http://127.0.0.1:9000/login
`-- /assets/*、/ -> /srv/www/app
这里的端口只是示例。生产环境可以让 wastnet 监听 80 或 443,后端服务只绑定到本机回环地址,避免直接暴露 API 端口。
这种路由方式有三个直接收益:
- 前端请求使用相对路径,例如
fetch('/api/users'),不需要把后端地址写进构建产物。 - 浏览器访问同一个源,开发和部署时的跨域配置更简单。
- 后端服务可以独立重启、扩容或迁移,外部入口保持不变。
一个可改造的部署配置
摘要没有给出 wastnet 的固定配置文件格式,因此下面示例采用“等价配置”的形式,字段名需要按照实际版本的配置 API 或启动参数调整。它表达的是可落地的路由规则,而不是声称某个版本一定支持这些原始字段。
server:
host: 0.0.0.0
port: 8080
static:
root: /srv/www/app
index: index.html
spa_fallback: /index.html
proxy:
- match: /api/
target: http://127.0.0.1:9000
strip_prefix: /api
preserve_host: true
timeout_ms: 10000
其中 strip_prefix 决定后端收到的路径。如果浏览器请求 /api/users,开启后后端收到的是 /users;如果后端路由本身包含 /api,则应关闭此前缀剥离,避免路径重复。
如果 wastnet 使用 Java API 注册路由,可以按下面的伪代码组织配置。实际类名和方法名应以项目版本为准:
public final class GatewayConfig {
public static void configure(Server server) {
server.listen("0.0.0.0", 8080);
server.proxy("/api/", ProxyTarget.http("http://127.0.0.1:9000")
.stripPrefix("/api")
.preserveHost(true)
.connectTimeoutMillis(2000)
.readTimeoutMillis(10000));
server.staticFiles("/srv/www/app")
.index("index.html")
.spaFallback("/index.html");
}
}
启动前准备一个最小的前端目录:
sudo mkdir -p /srv/www/app
printf '<!doctype html><html><body><h1>wastnet app</h1></body></html>\n' \
| sudo tee /srv/www/app/index.html >/dev/null
# 后端服务示例:实际项目应替换为自己的启动命令
java -jar backend.jar --server.port=9000
# 再启动 wastnet;参数名称按实际版本调整
java -jar wastnet.jar --config /etc/wastnet/app.yml
启动后可以分别检查静态资源和代理链路:
curl -i http://127.0.0.1:8080/
curl -i http://127.0.0.1:8080/api/health
如果 API 后端提供的是 /health,而代理规则剥离了 /api,第二个请求最终会访问后端的 /health。
前端路由、请求头与失败处理
单页应用通常需要处理浏览器直接访问 /dashboard 的情况。服务器找不到名为 dashboard 的物理文件时,应回退到 index.html,再由前端路由接管。这个回退规则只适用于页面请求,不应把 API 的 404 也回退成 HTML,否则前端会收到状态码正确但内容类型错误的响应。
反向代理还需要明确处理请求头。至少应关注以下信息:
Host:是否保留客户端访问的域名。X-Forwarded-For:记录真实客户端地址,后端日志和限流通常会用到。X-Forwarded-Proto:告诉后端外部请求是 HTTP 还是 HTTPS。Content-Length、Transfer-Encoding:转发请求体时必须保持协议语义一致。
可以在后端服务中按类似方式读取代理头,但只有在代理层可信时才应相信这些值:
String clientIp = request.getHeader("X-Forwarded-For");
if (clientIp == null || clientIp.isBlank()) {
clientIp = request.getRemoteAddr();
}
String scheme = request.getHeader("X-Forwarded-Proto");
if (scheme == null || scheme.isBlank()) {
scheme = request.getScheme();
}
生产环境还要为上游不可用设计清晰的行为。后端连接失败时,代理应返回 502 Bad Gateway;读取超时应返回 504 Gateway Timeout,并记录上游地址、请求路径和耗时。不要把所有代理错误都伪装成前端 index.html,否则排查问题时很难区分页面不存在和 API 服务故障。
HTTP/2 与部署边界
wastnet 的 HTTP/2 能力覆盖 h2 和 h2c,并且协议栈中的 HPACK、Huffman 和 ALPN 由项目自行实现。对于浏览器生产访问,通常会使用 TLS 加密的 h2;h2c 更适合受控的内网或调试场景,不能简单等同于面向公网的 HTTPS 部署。
可以这样验证客户端实际使用的协议:
curl -vk --http2 https://example.com/
curl -v --http1.1 http://127.0.0.1:8080/api/health
是否启用 HTTP/2、TLS 证书如何配置、代理是否支持 WebSocket 或流式响应,都应以当前 wastnet 版本的文档和 API 为准。HTTP/2 不会自动解决后端慢查询、连接池不足或大响应缓冲等问题;它主要改变客户端与入口服务器之间的传输方式。
上线前检查清单
部署这个模式时,可以逐项确认:
- wastnet 的监听地址和端口已确定,防火墙只开放统一入口。
- 后端 API 只绑定
127.0.0.1或受控内网地址。 /api/的路径是否需要剥离前缀已经和后端路由对齐。- 前端 SPA 回退不会吞掉 API 的
404、502或504。 - 静态资源启用了缓存策略,HTML 入口文件保留较短缓存时间。
- 代理超时、最大请求体、上传文件大小和连接数限制符合业务需求。
- 日志同时记录入口请求、上游状态、响应耗时和关联请求 ID。
- 在启用 HTTP/2 或 TLS 后,使用
curl和浏览器开发者工具确认协商结果。
一个端口统一托管前端和 API,真正的价值不只是减少端口数量,而是把浏览器看到的访问边界固定下来。wastnet 的零依赖 NIO 架构和自研 HTTP/2 协议栈适合希望控制服务器组成的 Java 项目;但落地时仍应以实际版本的配置能力、压测数据和故障行为为依据,逐步验证静态文件、代理请求、超时和协议协商这几条关键链路。