-
Notifications
You must be signed in to change notification settings - Fork 2
Compat WebServer Errors
Reference for WebServer module error types and handling patterns.
CompatError
└── ServerError
├── ServerConfigurationError
├── ServerPermissionError
├── ServerAlreadyRunningError
└── ServerNotRunningError
All server errors extend ServerError, which extends the base CompatError class. Each error includes:
-
message- Human-readable description -
mode- Server mode ('TCP' or 'UNIX') when applicable -
operation- What operation failed -
cause- Original error (if wrapping)
Base class for all server-related errors.
class ServerError extends CompatError {
readonly mode: ServerMode | 'N/A';
readonly operation: string;
constructor(
message: string,
mode: ServerMode | 'N/A',
operation: string,
cause?: Error,
);
toJSON(): object;
}When thrown:
- Generic server failures
- Wrapping unknown errors from runtime APIs
- Failures during start/stop operations
import { ServerError, type WebServer } from '@tundralibs/compat/webserver';
declare const server: WebServer;
try {
server.start();
} catch (error) {
if (error instanceof ServerError) {
console.log(`Operation failed: ${error.operation}`);
console.log(`Mode: ${error.mode}`);
console.log(`Message: ${error.message}`);
if (error.cause instanceof Error) {
console.log(`Caused by: ${error.cause.message}`);
}
}
}Thrown when server options are invalid.
class ServerConfigurationError extends ServerError {
/** Option that failed validation (e.g. `'port'`, `'tls.certFile'`). */
readonly option: string;
/** The rejected value, as supplied. */
readonly value: unknown;
/** What a valid value looks like, when the thrower described it. */
readonly expected?: string;
constructor(
mode: ServerMode | 'N/A',
option: string,
value: unknown,
expected?: string,
);
}The three values are exposed as readonly properties and included in
toJSON(), so a caller can branch on err.option instead of parsing
the message.
When thrown:
- Invalid port number
- Missing required options
- Invalid TLS configuration
- Invalid handler function
- Invalid socket path
import { WebServer } from '@tundralibs/compat/webserver';
// Invalid port
new WebServer('API', {
mode: 'TCP',
port: 99999, // > 65535
handler: () => new Response('OK'),
});
// Throws: ServerConfigurationError
// option: 'port'
// value: 99999
// expected: 'a valid port number (0 to 65535)'
// Missing handler
new WebServer('API', {
mode: 'TCP',
port: 8080,
handler: null as any,
});
// Throws: ServerConfigurationError
// option: 'handler'
// expected: 'a function'Handling:
import {
ServerConfigurationError,
type ServerOptions,
WebServer,
} from '@tundralibs/compat/webserver';
declare const config: ServerOptions;
try {
const server = new WebServer('API', config);
} catch (error) {
if (error instanceof ServerConfigurationError) {
// The offending key, its value and the expectation are folded
// into `message`; `operation` is always 'CONFIGURATION'.
console.error(`Invalid config: ${error.message}`);
console.error(`Mode: ${error.mode}, operation: ${error.operation}`);
}
}Thrown when the server lacks required permissions.
class ServerPermissionError extends ServerError {
constructor(message: string, mode: ServerMode);
}When thrown:
- Cannot read TLS certificate file
- Cannot read TLS key file
- Cannot write to UNIX socket directory
import { WebServer } from '@tundralibs/compat/webserver';
// Unreadable certificate
new WebServer('API', {
mode: 'TCP',
port: 443,
tls: {
certFile: '/root/secret/cert.pem', // No read permission
keyFile: '/root/secret/key.pem',
},
handler: () => new Response('OK'),
});
// Throws: ServerPermissionError
// message: "Insufficient permissions to read certificate file..."Handling:
import {
type ServerOptions,
ServerPermissionError,
WebServer,
} from '@tundralibs/compat/webserver';
declare const config: ServerOptions;
try {
const server = new WebServer('API', config);
} catch (error) {
if (error instanceof ServerPermissionError) {
console.error('Permission denied:', error.message);
console.error('Check file permissions and ownership');
}
}Thrown when attempting to start an already-running server.
class ServerAlreadyRunningError extends ServerError {
constructor(mode: ServerMode, operation: string);
}When thrown:
- Calling
start()when state is not 'STOPPED'
import type { WebServer } from '@tundralibs/compat/webserver';
declare const server: WebServer;
server.start();
server.start(); // Throws ServerAlreadyRunningErrorHandling:
import {
ServerAlreadyRunningError,
type WebServer,
} from '@tundralibs/compat/webserver';
declare const server: WebServer;
try {
server.start();
} catch (error) {
if (error instanceof ServerAlreadyRunningError) {
console.log('Server is already running');
// Maybe that's okay, or restart:
await server.stop();
server.start();
}
}Thrown when attempting operations on a stopped server.
class ServerNotRunningError extends ServerError {
constructor(mode: ServerMode, operation: string);
}When thrown:
- Calling
stop()when not running - Calling
ref()when not running - Calling
unref()when not running
import type { WebServer } from '@tundralibs/compat/webserver';
declare const server: WebServer;
await server.stop();
await server.stop(); // Throws ServerNotRunningErrorHandling:
import {
ServerNotRunningError,
type WebServer,
} from '@tundralibs/compat/webserver';
declare const server: WebServer;
try {
await server.stop();
} catch (error) {
if (error instanceof ServerNotRunningError) {
console.log('Server was not running');
}
}The server emits onError events for runtime errors:
import { ServerError, type WebServer } from '@tundralibs/compat/webserver';
declare const server: WebServer;
server.on('onError', (name, error, request, info) => {
console.error(`[${name}] Error:`, error.message);
if (error instanceof ServerError) {
console.error(` Operation: ${error.operation}`);
console.error(` Mode: ${error.mode}`);
}
if (request) {
console.error(` Request: ${request.method} ${request.url}`);
}
if (info) {
console.error(` Request ID: ${info.requestId}`);
console.error(` Remote: ${info.remoteAddress}:${info.remotePort}`);
}
});Events include optional request context when:
- Error occurred during request handling
- Handler threw an exception
import {
ServerConfigurationError,
type ServerOptions,
WebServer,
} from '@tundralibs/compat/webserver';
function createServer(config: ServerOptions): WebServer | null {
try {
return new WebServer('API', config);
} catch (error) {
if (error instanceof ServerConfigurationError) {
console.error(`Config error: ${error.message}`);
return null;
}
throw error; // Re-throw unexpected errors
}
}import { ServerError, type WebServer } from '@tundralibs/compat/webserver';
async function startWithRetry(
server: WebServer,
maxRetries = 3,
): Promise<void> {
for (let i = 0; i < maxRetries; i++) {
try {
server.start();
return;
} catch (error) {
if (error instanceof ServerError && i < maxRetries - 1) {
const delay = Math.pow(2, i) * 1000;
console.log(`Start failed, retrying in ${delay}ms...`);
await new Promise((r) => setTimeout(r, delay));
} else {
throw error;
}
}
}
}import type { WebServer } from '@tundralibs/compat/webserver';
declare const logger: { error(entry: unknown): void };
function setupErrorHandling(server: WebServer): void {
server.on('onError', (name, error, request, info) => {
const logEntry = {
timestamp: new Date().toISOString(),
server: name,
error: error.toJSON(),
request: request
? {
method: request.method,
url: request.url,
headers: Object.fromEntries(request.headers),
}
: null,
info: info
? {
requestId: info.requestId,
remoteAddress: info.remoteAddress,
requestTime: info.requestTime.toISOString(),
}
: null,
};
// Log to file, send to monitoring service, etc.
logger.error(logEntry);
});
}import { type RequestInfo, WebServer } from '@tundralibs/compat/webserver';
declare function handleRequest(
req: Request,
info: RequestInfo,
): Promise<Response>;
declare class ValidationError extends Error {
readonly fields: string[];
}
declare class NotFoundError extends Error {}
const server = new WebServer('API', {
mode: 'TCP',
port: 8080,
handler: async (req, info) => {
try {
return await handleRequest(req, info);
} catch (error) {
// Custom error responses
if (error instanceof ValidationError) {
return Response.json(
{ error: error.message, fields: error.fields },
{ status: 400 },
);
}
if (error instanceof NotFoundError) {
return Response.json(
{ error: 'Resource not found' },
{ status: 404 },
);
}
// Let server handle unknown errors (returns 500)
throw error;
}
},
});Error: EADDRINUSE or similar
Solution:
import type { WebServer } from '@tundralibs/compat/webserver';
declare const server: WebServer;
declare const port: number;
try {
server.start();
} catch (error) {
const cause = (error as Error).cause as { code?: string } | undefined;
if (cause?.code === 'EADDRINUSE') {
console.error(`Port ${port} is already in use`);
// Try different port, or kill existing process
}
}Error: UNIX socket file from previous run
The server automatically removes existing socket files, but if issues persist:
import { removeSync } from '@tundralibs/compat/file';
import type { WebServer } from '@tundralibs/compat/webserver';
declare const server: WebServer;
// Manual cleanup before starting
try {
removeSync('/var/run/myapp.sock');
} catch {}
server.start();Error: ServerConfigurationError for TLS files
Solution:
import { isFileSync } from '@tundralibs/compat/file';
declare const config: { tls: { certFile: string } };
// Validate before creating server
if (!isFileSync(config.tls.certFile)) {
console.error(`Certificate not found: ${config.tls.certFile}`);
process.exit(1);
}Error: Unhandled exception in request handler
The server catches handler exceptions and returns 500, but you should handle errors:
import type { ServerHandler } from '@tundralibs/compat/webserver';
declare function processRequest(req: Request): Promise<Response>;
const handler: ServerHandler = async (req, info) => {
try {
return await processRequest(req);
} catch (error) {
// Log the error
console.error(`Request failed:`, error);
// Return appropriate response
return new Response('Something went wrong', {
status: 500,
headers: { 'Content-Type': 'text/plain' },
});
}
};Error: A request whose Host header cannot be parsed into a valid URL
(for example an out-of-range port such as Host: example:99999999999).
Handling differs by runtime, because each builds the Request differently:
-
Node reconstructs the request URL from the client-supplied
Hostheader before dispatch. A value that Node's HTTP parser accepts but WHATWG URL parsing rejects would otherwise throw and crash the process, so the server rejects it with400 Bad Requestbefore your handler runs (and a WebSocket upgrade carrying a malformedHostheader is dropped by closing the socket). Your handler is not invoked. -
Deno and Bun build the
Requestfrom their native HTTP layer, so the request is dispatched to your handler, withreq.urlset to the unparseable string (for examplehttp://example:99999999999/). The server does not reject it first. If your handler then callsnew URL(req.url)— the idiom used throughout these docs — that call throws; the server catches it and answers500 Internal Server Error.
Because of this divergence, on Deno and Bun you should guard URL parsing (or
validate the Host header) if untrusted clients can send a malformed Host:
import type { ServerHandler } from '@tundralibs/compat/webserver';
const handler: ServerHandler = (req, info) => {
let url: URL;
try {
url = new URL(req.url);
} catch {
return new Response('Bad Request', { status: 400 });
}
// ...use `url` safely
return new Response('OK');
};Error: Graceful stop taking too long
Solution:
import type { WebServer } from '@tundralibs/compat/webserver';
declare const server: WebServer;
const stopTimeout = setTimeout(() => {
console.warn('Graceful stop timeout, forcing...');
server.stop(false).catch(console.error);
}, 30000);
await server.stop();
clearTimeout(stopTimeout);