Skip to content

krulod/node-wtransport

Repository files navigation

node-wtransport

Node.js server implementation of the WebTransport protocol, using the WTransport Rust library.

Installation

pnpm add wtransport # or whichever package manager you are using

This library is ESM-only.

Currently, it only works on Linux OS on x86-64 and ARM CPUs (support for other platforms is coming soon).

Usage

  1. obtain an identity
  2. create a server
  3. specify how requests are handled

API

Server

Servers are created using the WtServer class accepting these parameters:

  1. config: the server configuration object
  2. handler: the server handler object

WtServer objects have these members:

  • close(errorCode, reason): closes all connections immediately and ceases accepting new connections
  • idle: a promise that resolves when all connections are shut down
  • address: the local address the underlying socket is bound to
  • openConnections: the number of currently open connections
  • patchConfig(config): updates some properties of the server config, rebinding to a new socket if needed

Once created, a server immediately begins to listen for requests until close-d.

Example:

import {WtServer, WtIdentityFromPemFiles} from 'wtransport'

const server = new WtServer( {
	identity: WtIdentityFromPemFiles('certificate_chain.pem', 'private_key.pem'),
	bind: 7777,
}, {} )

server.address() //⇒ {address: '::', port: 7777, stack: 'v6'} or something like that

Config

  • common properties of a config
    • identity: a WtIdentity
    • maxIdleTimeout?: duration of inactivity to accept before timing out the connection; defaults to an infinite timeout
    • keepAliveInterval?: period of inactivity before sending a keep-alive packet; must be set lower than maxIdleTimeout of both peers to be effective; defaults to not sending keep-alive packets
  • mutually exclusive property groups specifying where the server should listen for requests; one and only one of these groups may be specified
    • bind to a local socket
      • bind: the socket port
      • bindTo?: either 'local' (LOCALHOST) or 'any' (INADDR_ANY; listens on all local interfaces); default is 'any'
      • bindStack?: either 'v4', 'v6' or 'dual'; default is dual
    • bind to an IPv4 socket specified by the address
      • bindV4: the address
    • bind to an IPv6 socket specified by the address
      • bindV6: the address
      • bindV6Dual?: IPv6 dual stack config; either 'osDefault', 'deny' or 'allow'; default is 'osDefault'

Identity

WtIdentity objects represents TLS identities consisting of a certificate chain and a private key. There are several ways to obtain one:

  • WtIdentityFromPem(certificateChainPem, privateKeyPem): from .pem file contents
  • WtIdentityFromPemFiles(certificateChainPath, privateKeyPath): from .pem file paths

Note that it may be tricky to get the self-signed development certificates accepted by your browser. Consider reading this project's local development docs

Handler

The handler is an object through the members of which the client processing pipeline is configured. The members are the following:

  • onRequest: called after recieving an HTTP/3 request
  • onConnection: called after establishing a QUIC connection
  • onSession: called after establishing a WebTransport session

onRequest

Accepts an object of the following shape:

Valid return values are:

  • 'refuse': to reject this connection attempt
  • 'ignore': to ignore this connection attempt, not sending any packet in response
  • 'retry': to respond with a retry packet, requiring the client to retry with address validation
  • undefined: to accept this connection attempt

onConnection

Accepts an object of the following shape:

  • address: an UDP address of the peer
  • authority: the :authority field of the request
  • path: the :path field of the request
  • origin: the origin field of the request if present
  • userAgent: the user-agent field of the request if present
  • headers: all header fields associated with the request

Valid return values are:

  • 'forbidden': rejects the request by replying with 403 status code
  • 'notFound': rejects the client request by replying with 404 status code.
  • 'tooManyRequests': rejects the client request by replying with 429 status code.
  • undefined: to establish the session

onSession

Accepts an object of the following shape:

  • address: an UDP address of the peer; note that this may change during the session's lifetime
  • openUni(): opens a new outgoing unidirectional stream
  • openBi(): opens a new outgoing bidirectional stream
  • acceptUni(): waits for an incoming unidirectional stream to be available and returns it
  • acceptBi(): waits for an incoming bidirectional stream to be available and returns it
  • datagramRecieve(): waits for the next incoming datagram and returns its payload
  • datagramSend(payload): sends a datagram, suddenly
  • datagramMaxSize: the maximum size of datagrams that can be sent; undefined if datagrams are unsupported; may change according to variation in the path MTU estimate; is at least a little over a kilobyte unless the peer specifies a lesser limit
  • idUnstable: an unstable WebTransport session identifier
  • id: a stable identifier for this connection
  • rtt: current best estimate of this connection’s latency (round-trip-time) in milliseconds
  • closed: whether the session is closed
  • close(): immediately closes the connection

onSession handler is expected to return nothing.

Streams

All streams have these members:

  • id: numeric identifier of the stream

If a stream is readable (either incoming unidirectional or bidirectional), the following members are available:

  • readExact(count): reads exactly count bytes from the stream, failing if the stream finishes prematurely
  • readTo(buffer): reads data contiguously from the stream to buffer and returns the number of bytes read
  • readNext(limit): reads the next segment of data not exceeding limit bytes, returning null if the stream finishes prematurely
  • readChunk(limit): reads the next segment of data not exceeding limit bytes and returns a stream chunk or null if the stream finishes prematurely
  • stop(): discards unread data and notifies the peer to stop transmitting; the stream stops being readable after calling this

If a stream is writable (either outgoing unidirectional or bidirectional), the following members are available:

  • write(bytes): send bytes to the peer
  • priority: a getter/setter; data from streams with higher priority will be transmitted before data from streams with lower priority; changing the priority of a stream with pending data may only take effect after that data has been transmitted; using many different priority levels per connection may have a negative impact on performance
  • finish(): shuts down the stream gracefully
  • reset(errorCode?): closes the write stream immediately
  • stopped(): waits for the stream to be stopped for any reason, which is returned: 'closed', 'notConnected', 'quicProtocolError' or 'stopped'

Data types

Address

An address is an object of the following shape:

  • toString()
  • ip: an IPv4 or IPv6 address depending on stack
  • stack: 'v4 or 'v6'
  • port

Stream Chunk

A stream chunk is an object of the following shape:

  • bytes: data of the chunk
  • offset: absolute offset of the chunk in the stream

Examples

To run examples locally, download their sources and replace fragments of code related to obtaining an identity and importing the library. Alternatively, refer to this project's development docs to learn how to pnpm -C examples/<name> dev

Example: Echo Server

Source code: examples/echo_server

This example implements a WebTransport server that accepts a bidirectional stream and echoes back any data received from the client until the stream is closed.

import {WtIdentityFromPemFiles, WtServer} from 'wtransport'

new WtServer( {
	identity: WtIdentityFromPemFiles('certificate_chain.pem', 'private_key.key'),
	bind: 7777,
}, { async onSession(session) {
	const id = session.id
	console.log(`[${id}] established the session`)

	const stream = await session.acceptBi()
	console.log(`[${id}] accepted the bidirectional stream`)

	for(;;) {
		var chunk = await stream.readNext(1024)
		if(!chunk) break
		console.log(`[${id}] recieved bytes:`, chunk.values().toArray())
		await stream.write(chunk)
	}

	session.close()
	console.log(`[${id}] closed the session`)
} } )

console.log('server started')

Example: Datagram Echo Server

This example implements a WebTransport server that echoes back all recieved datagrams.

Source code: examples/datagram_echo_server

Snippet:

import {WtIdentityFromPemFiles, WtServer} from 'wtransport'

new WtServer( {
	identity: WtIdentityFromPemFiles('certificate_chain.pem', 'private_key.key'),
	bind: 7777,
}, { async onSession(session) {
	const id = session.id
	console.log(`[${id}] established the session`)

	for(;;) {
		const limit = session.datagramMaxSize
		if(!limit) {
			console.log(`[${id}] cannot accept datagrams`)
			break
		}

		const datagram = await session.datagramRecieve()
		console.log(`[${id}] recieved a datagram:`, datagram.values().toArray())

		for(
			let start = 0, end = limit;
			end < datagram.length;
			start = end, end += limit
		) {
			await session.datagramSend( datagram.slice(start, end) )
		}
	}

	session.close()
	console.log(`[${id}] closed the session`)
} } )

console.log('server started')

LICENSE

AGPL-3.0-or-later

About

WebTransport server for Node

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published