Effect 平台 Deno 套接字服务端升级:统一 node-shared 实现、新增 reader 级 TLS 升级支持与迁移指南
【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effect
导读
本篇基于 Effect 生态仓库中的变更说明(changeset)文件,讲解@effect/platform-deno在最新迭代中对DenoSocketServer的底层实现调整:它不再使用 Deno 原生实现,而是复用@effect/platform-node-shared的 Node 套接字服务端,从而让被接受的 TCP 连接支持以 reader 作用域(reader-scoped)为粒度的服务端 TLS 升级。你将掌握新的make/layer参数形态(host替代hostname、{ path }替代{ transport: "unix", path })、Deno 版本要求(2.8.3 及以上),以及本次变更在源码层面的实现原理与实测用例验证。
一、变更概览:一次针对 Deno 套接字服务端的底层重构
该变更说明(.changeset/pre/eff-972-deno-socket-server.md)同时涉及两个包,均以patch级别发布:
| 包名 | 变更级别 | 说明 |
|---|---|---|
@effect/platform-deno | patch | 套接字服务端实现切换到 node-shared 版本 |
@effect/platform-node-shared | patch | 底层能力被 Deno 平台复用,并承载 reader 作用域的 TLS 升级逻辑 |
核心结论一句话:Deno 平台现在使用 node-shared 的套接字服务端,因此被接受的 TCP 连接能够支持以 reader 作用域为单位的服务端 TLS 升级(即先以明文 TCP 接受连接、在读取阶段按需升级为 TLS 的 STARTTLS 式能力)。
从源码结构可以直接看到这一结论:Deno 平台的套接字服务端模块 packages/platform/deno/src/DenoSocketServer.ts 全文只有一行导出语句:
export * from "@effect/platform-node-shared/NodeSocketServer"也就是说DenoSocketServer现在等价于 node-shared 的NodeSocketServer命名空间,其make、layer、makeTls、layerTls、makeWebSocket、layerWebSocket等构造器与 Layer 全部来自后者。包级入口 packages/platform/deno/src/index.ts 中DenoSocketServer子模块的导出保持不变(export * as DenoSocketServer from "./DenoSocketServer.ts"),因此对外 API 命名不受影响。
二、为什么这样做:reader 作用域的 TLS 升级
2.1 变更动机
变更说明原文明确指出动机:
Use the node-shared socket server on Deno so accepted TCP connections support reader-scoped server TLS upgrades.
即:让 Deno 上被接受(accepted)的 TCP 连接具备 reader 作用域的服务端 TLS 升级能力。
从源码实现看,这一能力由Socket.Reader上的upgrade操作承载。在 packages/platform/node-shared/src/NodeSocket.ts 的fromDuplex中,reader返回{ pull, upgrade }两个成员,其中upgrade允许在读取阶段将底层连接升级为 TLS:
- 服务端场景(
tlsServer: true)下,fromDuplex使用secure事件作为升级完成标志(客户端场景使用secureConnect),参见 NodeSocket.ts 的const secureEvent = isServer ? "secure" : "secureConnect"; - 服务端升级要求同时提供
key与cert,否则以SocketUpgradeError失败;升级完成后会通过Tls.TLSSocket包装原始连接并继续透传读写,参见 NodeSocket.ts 的upgrade实现。
这正是“reader 作用域”的含义:升级动作不是发生在接受连接时,而是在 handler 获取 reader 之后、按需触发,每次连接的升级状态(upgradeAvailable)也由 reader 的 scope 生命周期管理,scope 结束即失效。
2.2 升级语义细节(Node 实现)
在 NodeSocket.ts 中,升级前会做参数校验:
- 服务端场景必须同时提供
key与cert,否则报错 "server TLS upgrade requires both key and cert"; - 客户端场景同样要求
key与cert成对出现; - 可选参数包括
ca、passphrase、alpnProtocols、requestCert、rejectUnauthorized,其中key与passphrase使用Redacted类型承载,避免密钥以明文形式直接暴露。
值得注意的是,Deno 平台的客户端升级走的是Deno.startTls(见 DenoSocket.ts 的fromConn),它不支持客户端证书,因此key、cert、passphrase、requestCert在 Deno 客户端升级中不生效,且rejectUnauthorized: false仅关闭主机名校验、证书链校验仍然生效。本次变更解决的是服务端一侧:让 Deno 服务端通过 node-shared 实现获得完整的、与 Node 一致的服务端 TLS 升级能力。
三、Breaking Changes 与迁移指南
变更说明列出的破坏性变更集中于DenoSocketServer.make与DenoSocketServer.layer的参数形态,以及运行时版本要求。下面逐一给出新旧对比与迁移方法。
3.1 TCP 监听:hostname→host
变更前(Deno 原生参数):
DenoSocketServer.make({ hostname: "127.0.0.1", port: 8080 })变更后(Node listen 选项,即node:net的Net.ServerOpts & Net.ListenOptions):
DenoSocketServer.make({ host: "127.0.0.1", port: 8080 })layer的用法同步变化:
DenoSocketServer.layer({ host: "127.0.0.1", port: 8080 })从源码看,make与layer的类型签名已切换为options: Net.ServerOpts & Net.ListenOptions(NodeSocketServer.ts),这正是 Nodenet.Server.listen的选项集合,因此支持host、port、backlog、exclusive、ipv6Only、reusePort等 Node 标准监听选项。
3.2 Unix 套接字:{ transport: "unix", path }→{ path }
变更前:
DenoSocketServer.make({ transport: "unix", path: "/tmp/echo.sock" })变更后:
DenoSocketServer.make({ path: "/tmp/echo.sock" })layer同理:
DenoSocketServer.layer({ path: "/tmp/echo.sock" })在 node-shared 实现中,服务端监听后会把绑定的地址归一化为NetAddress.SocketAddress:字符串形式的server.address()映射为UnixPathAddress,AddressInfo对象映射为InetAddressV4/InetAddressV6(见 NodeSocketServer.ts 的socketAddressFromNode)。因此你可以通过server.address拿到标准化的NetAddress结果,测试中即以此断言地址类型与端口。
3.3 运行时要求:Deno 2.8.3 及以上
变更说明明确:Deno 2.8.3 或更新版本为必需条件。原因是 node-shared 套接字服务端依赖 Deno 的node:net/node:tls兼容层。源码中对此有专门适配:
- NodeSocketServer.ts 通过
const isDeno = "Deno" in globalThis检测运行环境; - 在 Deno 下不显式调用
conn.pause(),因为 Deno 的node:net兼容层在显式 pause 之后会破坏readable事件分发(见 NodeSocketServer.ts 与 NodeSocket.ts 的注释说明)。
这意味着升级到该版本前,请确认部署环境的 Deno 版本不低于 2.8.3,否则可能出现兼容层行为异常。
四、源码级解析:node-shared 服务端如何工作
理解本次变更,建议通读 packages/platform/node-shared/src/NodeSocketServer.ts。该模块把node:net的 TCP/Unix 服务端、node:tls的 TLS 服务端以及ws的 WebSocket 服务端统一封装为 scoped 的SocketServer.SocketServer服务,核心是内部函数makeNetServer(NodeSocketServer.ts):
- 创建并监听:
createServer()创建底层 server,随后server.listen(listenOptions),并通过Deferred+raceFirst将监听错误(如端口占用)转化为SocketServerError; - 挂起连接:在
run被调用之前,所有到达的连接被记录在pendingMap 中并保持暂停,避免数据丢失; - 按需接管:
run(handler)被调用时,把之前的 pending 连接逐一交给新的onConnection回调,用Fiber.runIn(scope)将每个连接的 handler 织入独立 Fiber,并在 handler 结束时关闭 scope; - 作用域清理:服务端作为
Effect.acquireRelease资源,scope 结束时销毁所有 pending 连接并关闭底层 server(NodeSocketServer.ts)。
每个被接受的连接会通过NodeSocket.fromDuplex(..., { tlsServer: true })包装为Socket.Socket,同时以Context提供NodeSocket.NetSocket服务标签,让 handler 能访问底层 Nodenet.Socket(NodeSocketServer.ts)。
TLS 服务端则使用makeTls/layerTls:监听secureConnection事件,握手失败的连接被直接销毁、服务端继续监听(NodeSocketServer.ts)。
五、迁移后的正确用法(附测试用例佐证)
仓库测试 packages/platform/deno/test/DenoSocketServer.test.ts 完整演示了迁移后的 API 形态,可直接作为迁移参考。
5.1 TCP echo 服务端
import * as DenoSocketServer from "@effect/platform-deno/DenoSocketServer" const server = yield* DenoSocketServer.make({ host: "127.0.0.1", port: 0 }) const address = server.address // address._tag === "InetAddressV4" yield* server.run(echo).pipe(Effect.forkScoped)注意port: 0表示由系统分配空闲端口,实际端口可通过server.address获取(测试断言address.port不为 0)。客户端连接使用DenoSocket.makeTcp({ hostname, port })(注意:客户端连接参数仍是hostname,只有服务端监听参数改为host,两者不要混淆)。
5.2 Unix 套接字 echo 服务端
const server = yield* DenoSocketServer.make({ path: "/tmp/echo.sock" }) // server.address._tag === "UnixPathAddress" yield* server.run(echo).pipe(Effect.forkScoped) // 客户端连接保持原有形态 const socket = yield* DenoSocket.makeTcp({ transport: "unix", path })5.3 服务端 TLS 升级(本次变更的核心能力)
测试用例 "upgrades a plain server connection to TLS"(DenoSocketServer.test.ts)演示了如何在 handler 中对普通 TCP 连接按需升级为 TLS:
const server = yield* DenoSocketServer.make({ host: "127.0.0.1", port: 0 }) yield* server.run((socket) => echo(socket, { cert, key: Redacted.make(key) // 密钥以 Redacted 承载 }) ).pipe(Effect.forkScoped) // 客户端同样在 reader 阶段升级,并传入 CA 信任锚 const socket = yield* DenoSocket.makeTcp({ hostname, port }) const output = yield* sendHelloDeno(socket, { ca: [ca] })其中服务端 handler 的核心逻辑为:
const { pull, upgrade } = yield* socket.reader if (upgradeOptions !== undefined) { yield* upgrade(upgradeOptions) // 读取阶段按需升级 TLS }这正是“reader 作用域的 TLS 升级”:服务端以明文 TCP 接受连接,handler 在 reader 阶段调用upgrade完成STARTTLS式升级,之后读写都走 TLS 通道。测试使用仓库内置的测试证书(packages/platform/deno/test/fixtures/tls/ 下的ca.pem、cert.pem、key.pem),断言升级后仍能正确完成 echo 往返。
5.4 资源关闭语义
测试 "closes with a pending pre-run connection"(DenoSocketServer.test.ts)验证了:在run尚未被调用时关闭服务端 scope,挂起的连接也会被正确清理,且关闭过程在 1 秒内完成。这印证了makeNetServer中 finalizer 对 pending 连接的销毁逻辑。
六、真实使用场景:Effect Cluster 在 Deno 上的套接字服务端
该变更并非孤立存在——DenoSocketServer正是 Effect Cluster 在 Deno 平台上的套接字传输底座。packages/platform/deno/src/DenoClusterSocket.ts 中的layerSocketServer直接使用迁移后的DenoSocketServer.layer:
return DenoSocketServer.layer({ host: listenAddress.value.host, port: listenAddress.value.port })其监听地址取自ShardingConfig.runnerListenAddress(回退到runnerAddress)。这意味着本次参数迁移会同步影响Deno 平台上 Cluster runner 的套接字监听配置——在 Deno 2.8.3+ 下,Cluster 套接字服务端同样获得 reader 作用域 TLS 升级能力,为跨节点加密通信(例如在不可信网络上运行的 runner 间传输)提供了按连接粒度开启 TLS 的手段。
七、迁移清单与注意事项
升级前请对照以下清单完成迁移:
- 升级 Deno 运行时到 2.8.3 或更新版本;
- 搜索代码中
DenoSocketServer.make/DenoSocketServer.layer的所有调用点,将 TCP 监听的hostname参数改为host; - 将 Unix 监听的
{ transport: "unix", path }对象改为{ path }; - 确认客户端连接 API(
DenoSocket.makeTcp)参数不变,仍为{ hostname, port }或{ transport: "unix", path }; - 如需服务端 TLS 升级,在 handler 的 reader 阶段调用
upgrade({ key, cert }),其中key使用Redacted.make包装,key与cert必须成对提供; - 若代码中依赖
server.address,注意其类型已归一化为NetAddress.SocketAddress(TCP 为InetAddressV4/InetAddressV6,Unix 为UnixPathAddress),读取 IP 使用NetAddress.formatIp,读取 Unix 路径使用.path; - 回归验证 Cluster 场景(如适用):
layerSocketServer的监听参数已随迁移改变,确认ShardingConfig中的监听地址配置与新的host参数语义一致。
结语
本次@effect/platform-deno的 patch 变更,通过将DenoSocketServer收敛到@effect/platform-node-shared的 Node 实现,为 Deno 平台补齐了 reader 作用域的服务端 TLS 升级能力,同时带来两处明确的破坏性参数变更与一个版本门槛。源码与测试均已同步更新(DenoSocketServer.ts、NodeSocketServer.ts、DenoSocketServer.test.ts),按上文清单迁移即可平滑升级,并在 Deno 上获得与 Node 一致、按连接粒度可控的 TLS 套接字服务能力。
【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effect
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考