EN

网络

统一的网络访问管理,支持 HTTP、TCP、TLS 与 UDP。

更新于

func/httpfunc/net 为需要访问外部系统的 Module 提供显式网络依赖。它们不会由 func 主入口自动导出;只有导入 HttpModuleNetModule 的 Module 才能注入对应客户端。

HTTP API

在远程 API 所属的 Module 中导入 HttpModule,然后把 HttpClient 注入 class-first API:

src/github/github.api.ts
import { Injectable, Module } from 'func'
import { HttpClient, HttpModule } from 'func/http'

@Injectable()
export class GitHubApi {
  constructor(private readonly http: HttpClient) {}

  async repository(owner: string, name: string) {
    const response = await this.http.request(
      `https://api.github.com/repos/${owner}/${name}`,
      { timeoutMs: 10_000 },
    )

    if (!response.ok) {
      throw new Error(`GitHub returned ${response.status}`)
    }

    return response.json()
  }
}

@Module({
  imports: [HttpModule],
  providers: [GitHubApi],
  exports: [GitHubApi],
})
export class GitHubModule {}

HttpClient.request() 接受 RequestURL 或字符串,以及兼容 RequestInit 的选项,并返回原生 Response。因此 response body 可以继续使用 json()text()arrayBuffer()body stream。

它有意保留 fetch 的响应语义,具体 API 应根据自己的协议检查 response.ok 或状态码。func 只把参数错误、网络失败、超时和取消转换为 HttpException。与直接使用全局 fetch 相比,HttpClient 额外提供:

  • 自动组合当前 Context.signal、输入 Request 的 signal 和单次请求 signal;
  • 使用 timeoutMs 限制请求;
  • invocation 结束时中止仍未完成的请求或响应流;
  • 通过 Provider override 测试,不需要修改全局 fetch;
  • 使用 HTTP_RUNTIME 提供稳定错误码。

HttpModule 不设置 base URL、认证、默认 headers 或业务重试策略。在此示例中,这些内容应留在 GitHubApi 等外部系统边界中,避免多个 API 共享隐式全局配置。

TCP、TLS 与 UDP

NetModule 导出三个用途明确的 Provider:

Provider底层实现返回值用途
TcpClientnode:netnet.SocketTCP 或 Unix domain socket 连接。
TlsClientnode:tlstls.TLSSocketTLS 客户端连接与握手。
UdpSocketFactorynode:dgramdgram.Socket创建 UDP4 或 UDP6 datagram socket。

对于 Redis、SMTP、设备控制或私有二进制协议,在 *.protocol.ts 中注入对应 client,并由协议类负责 framing、编码和响应解析:

src/echo/echo.protocol.ts
import { once } from 'node:events'
import { Injectable, Module } from 'func'
import { NetModule, TcpClient } from 'func/net'

@Injectable()
export class EchoProtocol {
  constructor(private readonly tcp: TcpClient) {}

  async exchange(message: string) {
    const socket = await this.tcp.connect({
      host: '127.0.0.1',
      port: 7000,
      timeoutMs: 3_000,
    })

    const received = once(socket, 'data')
    socket.end(message)
    const [data] = await received
    return data.toString()
  }
}

@Module({
  imports: [NetModule],
  providers: [EchoProtocol],
  exports: [EchoProtocol],
})
export class EchoModule {}
  • TcpClient.connect() 支持 Node.js TCP 或 IPC connection options;
  • TlsClient.connect() 支持 Node.js TLS connection options;
  • UdpSocketFactory.open() 默认创建 udp4 socket,也可以传入 { type: 'udp6' } 和其他 dgram.SocketOptions

返回值保持为 Node.js 原生 socket,因此 backpressure、stream、half-close、TLS 证书和 UDP bind/connect 等行为继续遵循 Node.js API。func 不定义通用的 request() 或协议注册表。

作用域与清理

网络 Provider 按 invocation 创建,并绑定当前 Context

  • Module 必须直接导入 HttpModuleNetModule 才能注入对应 Provider;
  • 导出的 API 服务或协议不会把底层网络 client 一起泄漏给调用方;
  • 创建的请求和 socket 会被跟踪,Provider dispose 时会中止请求、销毁 TCP/TLS 连接并关闭 UDP socket;
  • 不要在 invocation 结束后继续保存或使用返回的 Response、stream 或 socket。

直接调用 new HttpClient()new TcpClient() 等实例不会绕过 Module 注册;首次使用时会产生 NOT_REGISTERED 错误。

取消与进程信号

即使没有启用 func/signals,每次 invocation 也一定拥有 Context.signal,所以网络 Module 不需要额外导入 signal feature。单次调用提供的 signal 会和 Context signal 合并,任意来源取消都会停止对应操作。

需要让 Ctrl+CSIGINTSIGTERM 中止网络操作时,再显式启用 withProcessSignals()。测试和命令式调用则通过 invoke(..., { signal }) 提供取消来源。

测试

func/http/testing 提供严格的 HTTP expectation 和标准 Provider override:

tests/github.test.ts
import { createHttpMock } from 'func/http/testing'
import { createTestingApp } from 'func/testing'

const http = createHttpMock()
http
  .expect('GET', 'https://api.github.com/repos/unix/func')
  .reply(JSON.stringify({ stars: 42 }), {
    headers: { 'content-type': 'application/json' },
  })

const app = createTestingApp(AppModule, {
  overrides: [http.override()],
})

await app.invoke()
http.verify()

func/net/testing 提供 createNetMock()。使用 expectTcp()expectTls()expectUdp() 声明预期操作,通过 overrides() 一次替换 NetModule 的三个 Provider,并在测试结束时调用 verify() 检查是否还有未发生的操作。

这些 mock 按调用顺序消费 expectation。出现未声明的网络操作、参数不匹配或缺少 reply/rejection 时会立即失败。

API 速查

入口API用途
func/httpHttpModule, HttpClient, HttpException, HTTP_RUNTIMEHTTP 请求、生命周期和错误契约。
func/http/testingcreateHttpMock()严格 HTTP expectation 与 Provider override。
func/netNetModule, TcpClient, TlsClient, UdpSocketFactoryTCP、TLS、UDP 与自定义协议传输。
func/netNetException, NET_RUNTIME网络连接和 socket 的稳定错误契约。
func/net/testingcreateNetMock()严格 socket expectation 与 Provider overrides。