diff --git a/docs/api/http.md b/docs/api/http.md index f6e6499dc4..22db37bcaa 100644 --- a/docs/api/http.md +++ b/docs/api/http.md @@ -513,6 +513,241 @@ Bun.serve({ }); ``` +## Server Lifecycle Methods + +### server.stop() - Stop the server + +To stop the server from accepting new connections: + +```ts +const server = Bun.serve({ + fetch(req) { + return new Response("Hello!"); + }, +}); + +// Gracefully stop the server (waits for in-flight requests) +await server.stop(); + +// Force stop and close all active connections +await server.stop(true); +``` + +By default, `stop()` allows in-flight requests and WebSocket connections to complete. Pass `true` to immediately terminate all connections. + +### server.ref() and server.unref() - Process lifecycle control + +Control whether the server keeps the Bun process alive: + +```ts +// Don't keep process alive if server is the only thing running +server.unref(); + +// Restore default behavior - keep process alive +server.ref(); +``` + +### server.reload() - Hot reload handlers + +Update the server's handlers without restarting: + +```ts +const server = Bun.serve({ + static: { + "/api/version": Response.json({ version: "v1" }), + }, + fetch(req) { + return new Response("v1"); + }, +}); + +// Update to new handler +server.reload({ + static: { + "/api/version": Response.json({ version: "v2" }), + }, + fetch(req) { + return new Response("v2"); + }, +}); +``` + +This is useful for development and hot reloading. Only `fetch`, `error`, and `static` handlers can be updated. + +## Per-Request Controls + + + +### server.timeout(Request, seconds) - Custom request timeouts + +Set a custom idle timeout for individual requests: + +```ts +const server = Bun.serve({ + fetch(req, server) { + // Set 60 second timeout for this request + server.timeout(req, 60); + + // Long operation + await someSlowOperation(); + + return new Response("Done!"); + }, +}); +``` + +Pass `0` to disable the timeout for a request. + +### server.requestIP(Request) - Get client information + +Get client IP and port information: + +```ts +const server = Bun.serve({ + fetch(req, server) { + const address = server.requestIP(req); + if (address) { + return new Response( + `Client IP: ${address.address}, Port: ${address.port}`, + ); + } + return new Response("Unknown client"); + }, +}); +``` + +Returns `null` for closed requests or Unix domain sockets. + +## Server Metrics + +### server.pendingRequests and server.pendingWebSockets + +Monitor server activity with built-in counters: + +```ts +const server = Bun.serve({ + fetch(req, server) { + return new Response( + `Active requests: ${server.pendingRequests}\n` + + `Active WebSockets: ${server.pendingWebSockets}`, + ); + }, +}); +``` + +### server.subscriberCount(topic) - WebSocket subscribers + +Get count of subscribers for a WebSocket topic: + +```ts +const server = Bun.serve({ + fetch(req, server) { + const chatUsers = server.subscriberCount("chat"); + return new Response(`${chatUsers} users in chat`); + }, + websocket: { + message(ws) { + ws.subscribe("chat"); + }, + }, +}); +``` + +### server.publish(topic, data, compress) - WebSocket Message Publishing + +The server can publish messages to all WebSocket clients subscribed to a topic: + +```ts +const server = Bun.serve({ + websocket: { + message(ws) { + // Publish to all "chat" subscribers + server.publish("chat", "Hello everyone!"); + }, + }, + + fetch(req) { + // ... + }, +}); +``` + +The `publish()` method returns: + +- Number of bytes sent if successful +- `0` if the message was dropped +- `-1` if backpressure was applied + +## WebSocket Configuration + +### WebSocket Handler Options + +When configuring WebSockets, several advanced options are available through the `websocket` handler: + +```ts +Bun.serve({ + websocket: { + // Maximum message size (in bytes) + maxPayloadLength: 64 * 1024, + + // Backpressure limit before messages are dropped + backpressureLimit: 1024 * 1024, + + // Close connection if backpressure limit is hit + closeOnBackpressureLimit: true, + + // Handler called when backpressure is relieved + drain(ws) { + console.log("Backpressure relieved"); + }, + + // Enable per-message deflate compression + perMessageDeflate: { + compress: true, + decompress: true, + }, + + // Send ping frames to keep connection alive + sendPings: true, + + // Handlers for ping/pong frames + ping(ws, data) { + console.log("Received ping"); + }, + pong(ws, data) { + console.log("Received pong"); + }, + + // Whether server receives its own published messages + publishToSelf: false, + }, +}); +``` + ## Benchmarks Below are Bun and Node.js implementations of a simple HTTP server that responds `Bun!` to each incoming `Request`. @@ -561,100 +796,174 @@ The `Bun.serve` server can handle roughly 2.5x more requests per second than Nod {% details summary="See TypeScript definitions" %} ```ts -interface Bun { - serve(options: { - development?: boolean; - error?: ( - request: ErrorLike, - ) => Response | Promise | undefined | Promise; - fetch(request: Request, server: Server): Response | Promise; - hostname?: string; - id?: string | null; - maxRequestBodySize?: number; - port?: string | number; - reusePort?: boolean; - tls?: TLSOptions | Array; - unix: string; - websocket: WebSocketHandler; - }): Server; -} +interface Server extends Disposable { + /** + * Stop the server from accepting new connections. + * @param closeActiveConnections If true, immediately terminates all connections + * @returns Promise that resolves when the server has stopped + */ + stop(closeActiveConnections?: boolean): Promise; -interface TLSOptions { - ca?: string | Buffer | BunFile | Array | undefined; - cert?: - | string - | Buffer - | BunFile - | Array - | undefined; - dhParamsFile?: string; - key?: - | string - | Buffer - | BunFile - | Array - | undefined; - lowMemoryMode?: boolean; - passphrase?: string; - secureOptions?: number | undefined; - serverName?: string; + /** + * Update handlers without restarting the server. + * Only fetch and error handlers can be updated. + */ + reload(options: Serve): void; + + /** + * Make a request to the running server. + * Useful for testing or internal routing. + */ + fetch(request: Request | string): Response | Promise; + + /** + * Upgrade an HTTP request to a WebSocket connection. + * @returns true if upgrade successful, false if failed + */ + upgrade( + request: Request, + options?: { + headers?: Bun.HeadersInit; + data?: T; + }, + ): boolean; + + /** + * Publish a message to all WebSocket clients subscribed to a topic. + * @returns Bytes sent, 0 if dropped, -1 if backpressure applied + */ + publish( + topic: string, + data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, + compress?: boolean, + ): ServerWebSocketSendStatus; + + /** + * Get count of WebSocket clients subscribed to a topic. + */ + subscriberCount(topic: string): number; + + /** + * Get client IP address and port. + * @returns null for closed requests or Unix sockets + */ + requestIP(request: Request): SocketAddress | null; + + /** + * Set custom idle timeout for a request. + * @param seconds Timeout in seconds, 0 to disable + */ + timeout(request: Request, seconds: number): void; + + /** + * Keep process alive while server is running. + */ + ref(): void; + + /** + * Allow process to exit if server is only thing running. + */ + unref(): void; + + /** Number of in-flight HTTP requests */ + readonly pendingRequests: number; + + /** Number of active WebSocket connections */ + readonly pendingWebSockets: number; + + /** Server URL including protocol, hostname and port */ + readonly url: URL; + + /** Port server is listening on */ + readonly port: number; + + /** Hostname server is bound to */ + readonly hostname: string; + + /** Whether server is in development mode */ + readonly development: boolean; + + /** Server instance identifier */ + readonly id: string; } interface WebSocketHandler { - backpressureLimit?: number; - close?( - ws: ServerWebSocket, - code: number, - reason: string, - ): void | Promise; - closeOnBackpressureLimit?: boolean; - drain?(ws: ServerWebSocket): void | Promise; - idleTimeout?: number; + /** Maximum WebSocket message size in bytes */ maxPayloadLength?: number; - message( - ws: ServerWebSocket, - message: string | Buffer, - ): void | Promise; - open?(ws: ServerWebSocket): void | Promise; + + /** Bytes of queued messages before applying backpressure */ + backpressureLimit?: number; + + /** Whether to close connection when backpressure limit hit */ + closeOnBackpressureLimit?: boolean; + + /** Called when backpressure is relieved */ + drain?(ws: ServerWebSocket): void | Promise; + + /** Seconds before idle timeout */ + idleTimeout?: number; + + /** Enable per-message deflate compression */ perMessageDeflate?: | boolean | { compress?: WebSocketCompressor | boolean; decompress?: WebSocketCompressor | boolean; }; - ping?(ws: ServerWebSocket, data: Buffer): void | Promise; - pong?(ws: ServerWebSocket, data: Buffer): void | Promise; - publishToSelf?: boolean; + + /** Send ping frames to keep connection alive */ sendPings?: boolean; + + /** Whether server receives its own published messages */ + publishToSelf?: boolean; + + /** Called when connection opened */ + open?(ws: ServerWebSocket): void | Promise; + + /** Called when message received */ + message( + ws: ServerWebSocket, + message: string | Buffer, + ): void | Promise; + + /** Called when connection closed */ + close?( + ws: ServerWebSocket, + code: number, + reason: string, + ): void | Promise; + + /** Called when ping frame received */ + ping?(ws: ServerWebSocket, data: Buffer): void | Promise; + + /** Called when pong frame received */ + pong?(ws: ServerWebSocket, data: Buffer): void | Promise; } -interface Server { - fetch(request: Request | string): Response | Promise; - publish( - compress?: boolean, - data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, - topic: string, - ): ServerWebSocketSendStatus; - ref(): void; - reload(options: Serve): void; - requestIP(request: Request): SocketAddress | null; - stop(closeActiveConnections?: boolean): void; - unref(): void; - upgrade( - options?: { - data?: T; - headers?: Bun.HeadersInit; - }, - request: Request, - ): boolean; +interface TLSOptions { + /** Certificate authority chain */ + ca?: string | Buffer | BunFile | Array; - readonly development: boolean; - readonly hostname: string; - readonly id: string; - readonly pendingRequests: number; - readonly pendingWebSockets: number; - readonly port: number; - readonly url: URL; + /** Server certificate */ + cert?: string | Buffer | BunFile | Array; + + /** Path to DH parameters file */ + dhParamsFile?: string; + + /** Private key */ + key?: string | Buffer | BunFile | Array; + + /** Reduce TLS memory usage */ + lowMemoryMode?: boolean; + + /** Private key passphrase */ + passphrase?: string; + + /** OpenSSL options flags */ + secureOptions?: number; + + /** Server name for SNI */ + serverName?: string; } ```