中文

Networking

Manage HTTP, TCP, TLS, and UDP access through explicit dependencies.

Updated

func/http and func/net give Modules explicit dependencies for reaching external systems. They are not re-exported by the main func entry point; a Module can inject their clients only after importing HttpModule or NetModule.

HTTP APIs

Import HttpModule into the Module that owns the remote integration, then inject HttpClient into a 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() accepts a Request, URL, or string plus options compatible with RequestInit, and returns the native Response. Response bodies therefore retain json(), text(), arrayBuffer(), and the body stream.

The client deliberately keeps fetch response semantics: each API boundary should check response.ok or the relevant status codes for its own protocol. func converts only invalid arguments, network failures, timeouts, and cancellation into HttpException.

Compared with calling global fetch directly, HttpClient adds:

  • Composition of the current Context.signal, the input Request signal, and the per-request signal.
  • A timeoutMs request deadline.
  • Cancellation of unfinished requests and response streams when the invocation ends.
  • Provider overrides for tests without modifying global fetch.
  • Stable error codes through HTTP_RUNTIME.

HttpModule does not define a base URL, authentication, default headers, or a business retry policy. Keep those concerns at external-system boundaries such as GitHubApi, rather than sharing implicit global configuration across unrelated APIs.

TCP, TLS, and UDP

NetModule exports three focused Providers:

ProviderRuntimeReturnsUse case
TcpClientnode:netnet.SocketTCP or Unix domain socket connections.
TlsClientnode:tlstls.TLSSocketTLS client connections and handshakes.
UdpSocketFactorynode:dgramdgram.SocketUDP4 or UDP6 datagram sockets.

For Redis, SMTP, device control, or a private binary protocol, inject the appropriate client into a *.protocol.ts class. That protocol class owns framing, encoding, and response parsing:

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() accepts Node.js TCP or IPC connection options.
  • TlsClient.connect() accepts Node.js TLS connection options.
  • UdpSocketFactory.open() creates a udp4 socket by default; pass { type: 'udp6' } or other dgram.SocketOptions when needed.

The returned values are native Node.js sockets. Backpressure, streams, half-closes, TLS certificates, and UDP bind/connect behavior continue to follow the Node.js APIs. func does not impose a generic request() method or a protocol registry.

Scope and cleanup

Network Providers are created per invocation and bound to the current Context:

  • A Module must import HttpModule or NetModule directly to inject its Providers.
  • Exporting an API service or protocol does not leak its low-level network client to consumers.
  • Requests and sockets are tracked; Provider disposal aborts requests, destroys TCP/TLS connections, and closes UDP sockets.
  • Do not retain or use a returned Response, stream, or socket after its invocation ends.

Constructing new HttpClient(), new TcpClient(), or a similar client directly does not bypass Module registration. The first operation fails with NOT_REGISTERED.

Cancellation and process signals

Every invocation has a Context.signal, even when func/signals is not enabled, so network Modules do not need to import the signals feature. A per-call signal is combined with the Context signal, and cancellation from any source stops the affected operation.

Enable withProcessSignals() when Ctrl+C, SIGINT, or SIGTERM should cancel network work. Tests and programmatic hosts instead supply cancellation through invoke(..., { signal }).

Testing

func/http/testing provides strict HTTP expectations and a standard 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 provides createNetMock(). Declare operations with expectTcp(), expectTls(), or expectUdp(), replace all three NetModule Providers with overrides(), and call verify() at the end of the test to detect any expected operations that never happened.

Both mock systems consume expectations in call order. An undeclared network operation, mismatched arguments, or a missing reply/rejection fails immediately.

API reference

EntryAPIPurpose
func/httpHttpModule, HttpClient, HttpException, HTTP_RUNTIMEHTTP requests, lifecycle, and error contracts.
func/http/testingcreateHttpMock()Strict HTTP expectations and Provider override.
func/netNetModule, TcpClient, TlsClient, UdpSocketFactoryTCP, TLS, UDP, and custom protocol transport.
func/netNetException, NET_RUNTIMEStable network and socket error contracts.
func/net/testingcreateNetMock()Strict socket expectations and overrides.