An HTTP/1.1 client, written from scratch for Node.js
—
Comprehensive error classes for different types of HTTP client failures with detailed error information and proper error handling patterns.
Undici provides specific error types for different failure scenarios to enable precise error handling.
/**
* All undici error types
*/
const errors: {
UndiciError: typeof UndiciError;
ConnectTimeoutError: typeof ConnectTimeoutError;
HeadersTimeoutError: typeof HeadersTimeoutError;
BodyTimeoutError: typeof BodyTimeoutError;
ResponseError: typeof ResponseError;
ResponseStatusCodeError: typeof ResponseStatusCodeError;
RequestRetryError: typeof RequestRetryError;
ClientDestroyedError: typeof ClientDestroyedError;
ClientClosedError: typeof ClientClosedError;
SocketError: typeof SocketError;
NotSupportedError: typeof NotSupportedError;
InvalidArgumentError: typeof InvalidArgumentError;
InvalidReturnValueError: typeof InvalidReturnValueError;
RequestAbortedError: typeof RequestAbortedError;
InformationalError: typeof InformationalError;
RequestContentLengthMismatchError: typeof RequestContentLengthMismatchError;
ResponseContentLengthMismatchError: typeof ResponseContentLengthMismatchError;
HeadersOverflowError: typeof HeadersOverflowError;
HTTPParserError: typeof HTTPParserError;
};/**
* Base class for all undici errors
*/
class UndiciError extends Error {
name: string;
code: string;
message: string;
}Errors related to network timeouts during different phases of the request.
/**
* Connection establishment timeout
*/
class ConnectTimeoutError extends UndiciError {
name: 'ConnectTimeoutError';
code: 'UND_ERR_CONNECT_TIMEOUT';
message: string;
}
/**
* Headers reception timeout
*/
class HeadersTimeoutError extends UndiciError {
name: 'HeadersTimeoutError';
code: 'UND_ERR_HEADERS_TIMEOUT';
message: string;
}
/**
* Body reception timeout
*/
class BodyTimeoutError extends UndiciError {
name: 'BodyTimeoutError';
code: 'UND_ERR_BODY_TIMEOUT';
message: string;
}Usage Examples:
import { request, errors } from 'undici';
try {
const response = await request('https://slow-server.example.com/data', {
headersTimeout: 5000, // 5 second headers timeout
bodyTimeout: 10000 // 10 second body timeout
});
} catch (error) {
if (error instanceof errors.ConnectTimeoutError) {
console.log('Failed to establish connection within timeout');
} else if (error instanceof errors.HeadersTimeoutError) {
console.log('Headers not received within timeout');
} else if (error instanceof errors.BodyTimeoutError) {
console.log('Response body not received within timeout');
}
}Errors related to HTTP response processing and status codes.
/**
* General HTTP response error
*/
class ResponseError extends UndiciError {
name: 'ResponseError';
code: 'UND_ERR_RESPONSE';
statusCode: number;
headers: Record<string, string | string[]>;
body: any;
}
/**
* HTTP status code error
*/
class ResponseStatusCodeError extends UndiciError {
name: 'ResponseStatusCodeError';
code: 'UND_ERR_RESPONSE_STATUS_CODE';
statusCode: number;
headers: Record<string, string | string[]>;
body: any;
}
/**
* Request retry error after exhausting retries
*/
class RequestRetryError extends UndiciError {
name: 'RequestRetryError';
code: 'UND_ERR_REQ_RETRY';
statusCode?: number;
data: {
count: number;
error: Error;
};
}Usage Examples:
import { request, errors } from 'undici';
try {
const response = await request('https://api.example.com/protected', {
method: 'GET',
throwOnError: true
});
} catch (error) {
if (error instanceof errors.ResponseStatusCodeError) {
console.log(`HTTP ${error.statusCode}: ${error.message}`);
console.log('Response headers:', error.headers);
console.log('Response body:', error.body);
// Handle specific status codes
if (error.statusCode === 401) {
console.log('Authentication required');
} else if (error.statusCode === 403) {
console.log('Access forbidden');
} else if (error.statusCode >= 500) {
console.log('Server error occurred');
}
} else if (error instanceof errors.RequestRetryError) {
console.log(`Request failed after ${error.data.count} retries`);
console.log('Original error:', error.data.error.message);
}
}Errors related to client lifecycle and state management.
/**
* Using destroyed client
*/
class ClientDestroyedError extends UndiciError {
name: 'ClientDestroyedError';
code: 'UND_ERR_DESTROYED';
message: 'The client is destroyed';
}
/**
* Using closed client
*/
class ClientClosedError extends UndiciError {
name: 'ClientClosedError';
code: 'UND_ERR_CLOSED';
message: 'The client is closed';
}
/**
* Request aborted by user
*/
class RequestAbortedError extends UndiciError {
name: 'RequestAbortedError';
code: 'UND_ERR_ABORTED';
message: 'Request aborted';
}Usage Examples:
import { Client, errors } from 'undici';
const client = new Client('https://api.example.com');
// Destroy client
await client.destroy();
try {
// This will throw ClientDestroyedError
await client.request({ path: '/data' });
} catch (error) {
if (error instanceof errors.ClientDestroyedError) {
console.log('Cannot use destroyed client');
}
}
// Abort request example
const controller = new AbortController();
// Abort after 2 seconds
setTimeout(() => controller.abort(), 2000);
try {
const response = await client.request({
path: '/slow-endpoint',
signal: controller.signal
});
} catch (error) {
if (error instanceof errors.RequestAbortedError) {
console.log('Request was aborted');
}
}Errors related to HTTP protocol violations and parsing failures.
/**
* Low-level socket errors
*/
class SocketError extends UndiciError {
name: 'SocketError';
code: 'UND_ERR_SOCKET';
socket: {
localAddress: string;
localPort: number;
remoteAddress: string;
remotePort: number;
};
}
/**
* Headers size exceeds limits
*/
class HeadersOverflowError extends UndiciError {
name: 'HeadersOverflowError';
code: 'UND_ERR_HEADERS_OVERFLOW';
message: 'Headers overflow';
}
/**
* Request body length mismatch
*/
class RequestContentLengthMismatchError extends UndiciError {
name: 'RequestContentLengthMismatchError';
code: 'UND_ERR_REQ_CONTENT_LENGTH_MISMATCH';
message: 'Request body length does not match content-length header';
}
/**
* Response body length mismatch
*/
class ResponseContentLengthMismatchError extends UndiciError {
name: 'ResponseContentLengthMismatchError';
code: 'UND_ERR_RES_CONTENT_LENGTH_MISMATCH';
message: 'Response body length does not match content-length header';
}
/**
* HTTP parsing errors
*/
class HTTPParserError extends Error {
name: 'HTTPParserError';
code: string;
bytesParsed: number;
}Usage Examples:
import { request, errors } from 'undici';
try {
const response = await request('https://malformed-server.example.com/data');
} catch (error) {
if (error instanceof errors.HTTPParserError) {
console.log('HTTP parsing failed:', error.message);
console.log('Bytes parsed:', error.bytesParsed);
} else if (error instanceof errors.HeadersOverflowError) {
console.log('Response headers too large');
} else if (error instanceof errors.ResponseContentLengthMismatchError) {
console.log('Response body length mismatch');
} else if (error instanceof errors.SocketError) {
console.log('Socket error:', error.message);
console.log('Socket info:', error.socket);
}
}Errors related to invalid arguments and return values.
/**
* Invalid function arguments
*/
class InvalidArgumentError extends UndiciError {
name: 'InvalidArgumentError';
code: 'UND_ERR_INVALID_ARG';
message: string;
}
/**
* Invalid return values
*/
class InvalidReturnValueError extends UndiciError {
name: 'InvalidReturnValueError';
code: 'UND_ERR_INVALID_RETURN_VALUE';
message: string;
}
/**
* Unsupported functionality
*/
class NotSupportedError extends UndiciError {
name: 'NotSupportedError';
code: 'UND_ERR_NOT_SUPPORTED';
message: string;
}Usage Examples:
import { request, errors } from 'undici';
try {
// Invalid URL will throw InvalidArgumentError
await request(null);
} catch (error) {
if (error instanceof errors.InvalidArgumentError) {
console.log('Invalid argument provided:', error.message);
}
}
try {
// Unsupported feature
await request('https://api.example.com', {
method: 'INVALID_METHOD'
});
} catch (error) {
if (error instanceof errors.NotSupportedError) {
console.log('Feature not supported:', error.message);
}
}import { request, errors } from 'undici';
async function safeRequest(url, options = {}) {
try {
return await request(url, {
...options,
headersTimeout: 10000,
bodyTimeout: 30000,
throwOnError: true
});
} catch (error) {
// Network and timeout errors
if (error instanceof errors.ConnectTimeoutError) {
throw new Error('Connection timeout - server may be down');
}
if (error instanceof errors.HeadersTimeoutError) {
throw new Error('Headers timeout - server may be overloaded');
}
if (error instanceof errors.BodyTimeoutError) {
throw new Error('Body timeout - response too large or slow');
}
// HTTP response errors
if (error instanceof errors.ResponseStatusCodeError) {
switch (error.statusCode) {
case 400:
throw new Error('Bad request - check your request data');
case 401:
throw new Error('Authentication required');
case 403:
throw new Error('Access forbidden');
case 404:
throw new Error('Resource not found');
case 429:
throw new Error('Rate limit exceeded');
case 500:
throw new Error('Internal server error');
case 502:
case 503:
case 504:
throw new Error('Server temporarily unavailable');
default:
throw new Error(`HTTP ${error.statusCode}: ${error.message}`);
}
}
// Client state errors
if (error instanceof errors.ClientDestroyedError) {
throw new Error('HTTP client was destroyed');
}
if (error instanceof errors.RequestAbortedError) {
throw new Error('Request was cancelled');
}
// Protocol errors
if (error instanceof errors.HTTPParserError) {
throw new Error('Invalid HTTP response from server');
}
// Validation errors
if (error instanceof errors.InvalidArgumentError) {
throw new Error('Invalid request parameters');
}
// Unknown error
throw new Error(`Unexpected error: ${error.message}`);
}
}import { request, errors } from 'undici';
async function requestWithRetry(url, options = {}, maxRetries = 3) {
let attempt = 0;
while (attempt <= maxRetries) {
try {
return await request(url, options);
} catch (error) {
attempt++;
// Don't retry client errors (4xx) or validation errors
if (
error instanceof errors.ResponseStatusCodeError &&
error.statusCode >= 400 && error.statusCode < 500
) {
throw error;
}
if (
error instanceof errors.InvalidArgumentError ||
error instanceof errors.NotSupportedError ||
error instanceof errors.ClientDestroyedError
) {
throw error;
}
// Retry on network errors, timeouts, and server errors
const shouldRetry =
error instanceof errors.ConnectTimeoutError ||
error instanceof errors.HeadersTimeoutError ||
error instanceof errors.BodyTimeoutError ||
error instanceof errors.SocketError ||
(error instanceof errors.ResponseStatusCodeError &&
error.statusCode >= 500);
if (!shouldRetry || attempt > maxRetries) {
throw error;
}
// Exponential backoff
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}import { request, errors } from 'undici';
function logError(error, context = {}) {
const errorInfo = {
timestamp: new Date().toISOString(),
name: error.name,
code: error.code,
message: error.message,
...context
};
// Add specific error details
if (error instanceof errors.ResponseStatusCodeError) {
errorInfo.statusCode = error.statusCode;
errorInfo.headers = error.headers;
}
if (error instanceof errors.RequestRetryError) {
errorInfo.retryCount = error.data.count;
errorInfo.originalError = error.data.error.message;
}
if (error instanceof errors.SocketError && error.socket) {
errorInfo.socket = error.socket;
}
console.error('HTTP Request Error:', JSON.stringify(errorInfo, null, 2));
// Send to monitoring service
// monitoringService.logError(errorInfo);
}
async function monitoredRequest(url, options = {}) {
try {
return await request(url, options);
} catch (error) {
logError(error, { url, method: options.method || 'GET' });
throw error;
}
}Install with Tessl CLI
npx tessl i tessl/npm-undici