package aceboot::http
import aceboot::web.*
import aceboot::router.pathregex.*
import stdx.net.http.*
import std.collection.*
/**
* WebSocket 端点注册表与升级分发(ace-http 侧,不依赖 ace-websocket;后者经 registerWsEndpoint 反向推送)。
* driver:拿到已升级的 WebSocket + 路径参数后自行收发(通常 onOpen + 帧循环 + onClose)。
*/
public type WsDriver = (WebSocket, HashMap<String, String>) -> Unit
class WsEndpointEntry {
let path: String
let driver: WsDriver
let matcher: CompiledMatcher
init(path: String, driver: WsDriver) {
this.path = path
this.driver = driver
this.matcher = CompiledMatcher(path, MatchOptions())
}
}
let _wsEndpoints = ArrayList<WsEndpointEntry>()
/** 注册 WebSocket 端点(路径支持 :param;由 ace-websocket 的 @WsController 推送)。 */
public func registerWsEndpoint(path: String, driver: WsDriver): Unit {
_wsEndpoints.add(WsEndpointEntry(path, driver))
}
public func wsEndpointCount(): Int64 {
_wsEndpoints.size
}
// 是否 WebSocket 升级请求:
// - HTTP/1.1:GET + `Upgrade: websocket` 头(RFC 6455)。
// - HTTP/2:extended CONNECT(RFC 8441,:method=CONNECT + :protocol=websocket,无 Upgrade 头);
// :protocol 伪头由 stdx 的 upgradeFromServer 校验,这里按 CONNECT 分流即可
// (非 ws 的 CONNECT 不会命中已注册端点,自然落回洋葱)。
func isWebSocketUpgrade(req: HttpRequest): Bool {
match (req.version) {
case HTTP2_0 => req.method == "CONNECT"
case _ => match (req.headers.getFirst("upgrade")) {
case Some(v) => toLowerAscii(v).contains("websocket")
case None => false
}
}
}
// 匹配 ws 端点(复用 ace-router path-to-regexp),命中返回 driver + 路径参数。
func matchWsEndpoint(path: String): ?(WsDriver, HashMap<String, String>) {
for (e in _wsEndpoints) {
let (ok, result) = e.matcher.matchDetailed(path)
if (ok) {
let params = HashMap<String, String>()
for (p in result.paramsText) {
params.add(p[0], p[1])
}
return (e.driver, params)
}
}
return None
}
// 升级并驱动 ws 会话;返回 true 表示已处理(dispatch 早退,不进洋葱)。
func handleWsUpgrade(httpCtx: HttpContext, path: String): Bool {
match (matchWsEndpoint(path)) {
case Some((driver, params)) =>
let ws = WebSocket.upgradeFromServer(httpCtx)
try {
driver(ws, params)
} catch (e: Exception) {
()
} finally {
ws.closeConn()
}
true
case None => false
}
}
/** 内置 echo 端点(测试/探活用):文本/二进制原样回发。 */
public func registerEchoWs(path: String): Unit {
registerWsEndpoint(path, {ws: WebSocket, _: HashMap<String, String> =>
while (true) {
let f = ws.read()
match (f.frameType) {
case TextWebFrame => ws.write(TextWebFrame, f.payload)
case BinaryWebFrame => ws.write(BinaryWebFrame, f.payload)
case PingWebFrame => ws.writePongFrame(f.payload)
case CloseWebFrame =>
ws.writeCloseFrame(status: 1000u16)
break
case _ => ()
}
}
})
}