or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

caching.mdconnection-management.mdcookies.mdcore-http.mderrors.mdglobal-config.mdheaders-body.mdindex.mdinterceptors.mdmock-testing.mdweb-standards.md

interceptors.mddocs/

0

# Interceptors and Middleware

1

2

Composable interceptor system for request/response processing with built-in interceptors for common needs like retries, caching, and compression.

3

4

## Capabilities

5

6

### Interceptor System

7

8

The interceptor system allows composing middleware-style request/response processing through dispatcher composition.

9

10

```javascript { .api }

11

/**

12

* Built-in interceptors for common HTTP client needs

13

*/

14

const interceptors: {

15

redirect(options?: RedirectInterceptorOpts): DispatcherComposeInterceptor;

16

retry(options?: RetryInterceptorOpts): DispatcherComposeInterceptor;

17

cache(options?: CacheInterceptorOpts): DispatcherComposeInterceptor;

18

decompress(options?: DecompressInterceptorOpts): DispatcherComposeInterceptor;

19

dump(options?: DumpInterceptorOpts): DispatcherComposeInterceptor;

20

dns(options?: DnsInterceptorOpts): DispatcherComposeInterceptor;

21

responseError(options?: ResponseErrorInterceptorOpts): DispatcherComposeInterceptor;

22

};

23

24

type DispatcherComposeInterceptor = (dispatch: Dispatcher['dispatch']) => Dispatcher['dispatch'];

25

26

interface Dispatcher {

27

compose(interceptors: DispatcherComposeInterceptor[]): ComposedDispatcher;

28

compose(...interceptors: DispatcherComposeInterceptor[]): ComposedDispatcher;

29

}

30

```

31

32

**Usage Examples:**

33

34

```javascript

35

import { Agent, interceptors } from 'undici';

36

37

// Create dispatcher with multiple interceptors

38

const agent = new Agent()

39

.compose(

40

interceptors.retry({ maxRetries: 3 }),

41

interceptors.redirect({ maxRedirections: 5 }),

42

interceptors.decompress()

43

);

44

45

// Use composed dispatcher

46

const response = await agent.request({

47

origin: 'https://api.example.com',

48

path: '/data'

49

});

50

```

51

52

### Redirect Interceptor

53

54

Automatic HTTP redirect handling with configurable limits and policies.

55

56

```javascript { .api }

57

/**

58

* HTTP redirect interceptor

59

* @param options - Redirect configuration

60

* @returns Interceptor function

61

*/

62

function redirect(options?: RedirectInterceptorOpts): DispatcherComposeInterceptor;

63

64

interface RedirectInterceptorOpts {

65

maxRedirections?: number;

66

throwOnMaxRedirections?: boolean;

67

}

68

```

69

70

**Usage Examples:**

71

72

```javascript

73

import { Pool, interceptors } from 'undici';

74

75

// Configure redirect behavior

76

const pool = new Pool('https://api.example.com')

77

.compose(interceptors.redirect({

78

maxRedirections: 10,

79

throwOnMaxRedirections: true

80

}));

81

82

// Requests automatically follow redirects

83

const response = await pool.request({

84

path: '/redirect-chain',

85

method: 'GET'

86

});

87

88

console.log(response.context.history); // Array of redirected URLs

89

```

90

91

### Retry Interceptor

92

93

Automatic request retry with exponential backoff and configurable retry conditions.

94

95

```javascript { .api }

96

/**

97

* Request retry interceptor

98

* @param options - Retry configuration

99

* @returns Interceptor function

100

*/

101

function retry(options?: RetryInterceptorOpts): DispatcherComposeInterceptor;

102

103

interface RetryInterceptorOpts {

104

retry?: (err: Error, context: RetryContext) => number | null;

105

maxRetries?: number;

106

maxTimeout?: number;

107

minTimeout?: number;

108

timeoutFactor?: number;

109

retryAfter?: boolean;

110

methods?: string[];

111

statusCodes?: number[];

112

errorCodes?: string[];

113

}

114

115

interface RetryContext {

116

state: RetryState;

117

opts: RetryInterceptorOpts;

118

}

119

120

interface RetryState {

121

counter: number;

122

currentTimeout: number;

123

}

124

```

125

126

**Usage Examples:**

127

128

```javascript

129

import { Agent, interceptors } from 'undici';

130

131

// Basic retry configuration

132

const agent = new Agent()

133

.compose(interceptors.retry({

134

maxRetries: 3,

135

methods: ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE'],

136

statusCodes: [408, 413, 429, 500, 502, 503, 504],

137

errorCodes: ['ECONNRESET', 'ECONNREFUSED', 'ENOTFOUND', 'ENETDOWN']

138

}));

139

140

// Custom retry logic with exponential backoff

141

const customRetryAgent = new Agent()

142

.compose(interceptors.retry({

143

retry: (err, { state, opts }) => {

144

const { counter, currentTimeout } = state;

145

146

if (counter >= opts.maxRetries) {

147

return null; // Stop retrying

148

}

149

150

// Exponential backoff with jitter

151

const delay = Math.min(

152

currentTimeout * Math.pow(2, counter),

153

opts.maxTimeout || 30000

154

);

155

156

return delay + Math.random() * 1000;

157

},

158

maxRetries: 5,

159

maxTimeout: 30000,

160

minTimeout: 1000,

161

retryAfter: true // Respect Retry-After header

162

}));

163

164

// Make request with retry

165

const response = await customRetryAgent.request({

166

origin: 'https://unreliable-api.example.com',

167

path: '/data'

168

});

169

```

170

171

### Cache Interceptor

172

173

HTTP caching interceptor with configurable cache stores and policies.

174

175

```javascript { .api }

176

/**

177

* HTTP caching interceptor

178

* @param options - Cache configuration

179

* @returns Interceptor function

180

*/

181

function cache(options?: CacheInterceptorOpts): DispatcherComposeInterceptor;

182

183

interface CacheInterceptorOpts {

184

store?: CacheStore;

185

methods?: string[];

186

vary?: string[];

187

cacheByDefault?: number;

188

type?: 'shared' | 'private';

189

}

190

191

interface CacheStore {

192

get(key: string): Promise<CacheValue | undefined>;

193

set(key: string, value: CacheValue, ttl?: number): Promise<void>;

194

delete(key: string): Promise<boolean>;

195

}

196

197

interface CacheValue {

198

statusCode: number;

199

statusMessage: string;

200

headers: Record<string, string>;

201

body: Buffer;

202

cacheControlDirectives: Record<string, string | boolean>;

203

vary: Record<string, string>;

204

}

205

```

206

207

**Usage Examples:**

208

209

```javascript

210

import { Agent, interceptors, cacheStores } from 'undici';

211

212

// Use memory cache store

213

const agent = new Agent()

214

.compose(interceptors.cache({

215

store: new cacheStores.MemoryCacheStore(),

216

methods: ['GET', 'HEAD'],

217

cacheByDefault: 300, // 5 minutes default TTL

218

type: 'private'

219

}));

220

221

// Use SQLite cache store for persistence

222

const persistentAgent = new Agent()

223

.compose(interceptors.cache({

224

store: new cacheStores.SqliteCacheStore('./http-cache.db'),

225

methods: ['GET'],

226

vary: ['user-agent', 'accept-encoding']

227

}));

228

229

// Make cacheable requests

230

const response1 = await agent.request({

231

origin: 'https://api.example.com',

232

path: '/users'

233

});

234

235

// Second request hits cache

236

const response2 = await agent.request({

237

origin: 'https://api.example.com',

238

path: '/users'

239

});

240

```

241

242

### Decompress Interceptor

243

244

Automatic response decompression for gzip, deflate, and brotli encodings.

245

246

```javascript { .api }

247

/**

248

* Response decompression interceptor

249

* @param options - Decompression configuration

250

* @returns Interceptor function

251

*/

252

function decompress(options?: DecompressInterceptorOpts): DispatcherComposeInterceptor;

253

254

interface DecompressInterceptorOpts {

255

encodings?: string[];

256

maxResponseSize?: number;

257

}

258

```

259

260

**Usage Examples:**

261

262

```javascript

263

import { Pool, interceptors } from 'undici';

264

265

// Enable automatic decompression

266

const pool = new Pool('https://api.example.com')

267

.compose(interceptors.decompress({

268

encodings: ['gzip', 'deflate', 'br'], // brotli, gzip, deflate

269

maxResponseSize: 10 * 1024 * 1024 // 10MB limit

270

}));

271

272

// Requests automatically include Accept-Encoding header

273

// and responses are decompressed transparently

274

const response = await pool.request({

275

path: '/compressed-data',

276

method: 'GET'

277

});

278

279

const data = await response.body.json();

280

```

281

282

### Dump Interceptor

283

284

Request/response dumping for debugging and logging purposes.

285

286

```javascript { .api }

287

/**

288

* Request/response dumping interceptor

289

* @param options - Dump configuration

290

* @returns Interceptor function

291

*/

292

function dump(options?: DumpInterceptorOpts): DispatcherComposeInterceptor;

293

294

interface DumpInterceptorOpts {

295

request?: boolean;

296

response?: boolean;

297

requestHeaders?: boolean;

298

responseHeaders?: boolean;

299

requestBody?: boolean;

300

responseBody?: boolean;

301

maxBodySize?: number;

302

logger?: (message: string) => void;

303

}

304

```

305

306

**Usage Examples:**

307

308

```javascript

309

import { Client, interceptors } from 'undici';

310

311

// Dump all request/response data

312

const client = new Client('https://api.example.com')

313

.compose(interceptors.dump({

314

request: true,

315

response: true,

316

requestHeaders: true,

317

responseHeaders: true,

318

requestBody: true,

319

responseBody: true,

320

maxBodySize: 1024, // Only dump first 1KB of body

321

logger: console.log

322

}));

323

324

// All requests will be logged

325

const response = await client.request({

326

path: '/debug',

327

method: 'POST',

328

body: JSON.stringify({ test: 'data' })

329

});

330

331

// Custom logger

332

const debugClient = new Client('https://api.example.com')

333

.compose(interceptors.dump({

334

response: true,

335

responseHeaders: true,

336

logger: (message) => {

337

// Custom logging logic

338

console.log(`[HTTP DEBUG] ${new Date().toISOString()} ${message}`);

339

}

340

}));

341

```

342

343

### DNS Interceptor

344

345

DNS caching and resolution interceptor for improved performance.

346

347

```javascript { .api }

348

/**

349

* DNS caching and resolution interceptor

350

* @param options - DNS configuration

351

* @returns Interceptor function

352

*/

353

function dns(options?: DnsInterceptorOpts): DispatcherComposeInterceptor;

354

355

interface DnsInterceptorOpts {

356

maxItems?: number;

357

maxTtl?: number;

358

lookup?: (hostname: string, options: any, callback: (err: Error | null, address: string, family: number) => void) => void;

359

}

360

```

361

362

**Usage Examples:**

363

364

```javascript

365

import { Agent, interceptors } from 'undici';

366

import { lookup } from 'dns';

367

368

// DNS caching for improved performance

369

const agent = new Agent()

370

.compose(interceptors.dns({

371

maxItems: 100, // Cache up to 100 DNS entries

372

maxTtl: 300000, // 5 minutes TTL

373

lookup: lookup // Use Node.js built-in DNS lookup

374

}));

375

376

// Subsequent requests to same hostname use cached DNS

377

const responses = await Promise.all([

378

agent.request({ origin: 'https://api.example.com', path: '/endpoint1' }),

379

agent.request({ origin: 'https://api.example.com', path: '/endpoint2' }),

380

agent.request({ origin: 'https://api.example.com', path: '/endpoint3' })

381

]);

382

```

383

384

### Response Error Interceptor

385

386

Enhanced error handling for HTTP response errors with detailed error information.

387

388

```javascript { .api }

389

/**

390

* Response error handling interceptor

391

* @param options - Error handling configuration

392

* @returns Interceptor function

393

*/

394

function responseError(options?: ResponseErrorInterceptorOpts): DispatcherComposeInterceptor;

395

396

interface ResponseErrorInterceptorOpts {

397

throwOnError?: boolean;

398

statusCodes?: number[];

399

includeResponseBody?: boolean;

400

maxResponseBodySize?: number;

401

}

402

```

403

404

**Usage Examples:**

405

406

```javascript

407

import { Pool, interceptors } from 'undici';

408

409

// Throw errors for 4xx and 5xx responses

410

const pool = new Pool('https://api.example.com')

411

.compose(interceptors.responseError({

412

throwOnError: true,

413

statusCodes: [400, 401, 403, 404, 500, 502, 503, 504],

414

includeResponseBody: true,

415

maxResponseBodySize: 1024

416

}));

417

418

try {

419

const response = await pool.request({

420

path: '/nonexistent',

421

method: 'GET'

422

});

423

} catch (error) {

424

console.log(error.statusCode); // 404

425

console.log(error.statusMessage); // Not Found

426

console.log(error.responseBody); // Error response body

427

}

428

```

429

430

## Custom Interceptors

431

432

Create custom interceptors for application-specific needs.

433

434

```javascript { .api }

435

/**

436

* Custom interceptor example

437

*/

438

function customInterceptor(options = {}) {

439

return (dispatch) => {

440

return (opts, handler) => {

441

// Pre-request processing

442

const modifiedOpts = {

443

...opts,

444

headers: {

445

...opts.headers,

446

'x-custom-header': 'custom-value'

447

}

448

};

449

450

// Wrap handler for post-response processing

451

const wrappedHandler = {

452

...handler,

453

onComplete(trailers) {

454

// Post-response processing

455

console.log('Request completed');

456

handler.onComplete(trailers);

457

},

458

onError(error) {

459

// Error processing

460

console.log('Request failed:', error.message);

461

handler.onError(error);

462

}

463

};

464

465

return dispatch(modifiedOpts, wrappedHandler);

466

};

467

};

468

}

469

```

470

471

**Usage Examples:**

472

473

```javascript

474

import { Agent } from 'undici';

475

476

// Authentication interceptor

477

function authInterceptor(token) {

478

return (dispatch) => {

479

return (opts, handler) => {

480

return dispatch({

481

...opts,

482

headers: {

483

...opts.headers,

484

'authorization': `Bearer ${token}`

485

}

486

}, handler);

487

};

488

};

489

}

490

491

// Rate limiting interceptor

492

function rateLimitInterceptor(requestsPerSecond = 10) {

493

let lastRequestTime = 0;

494

const minInterval = 1000 / requestsPerSecond;

495

496

return (dispatch) => {

497

return async (opts, handler) => {

498

const now = Date.now();

499

const timeSinceLastRequest = now - lastRequestTime;

500

501

if (timeSinceLastRequest < minInterval) {

502

await new Promise(resolve =>

503

setTimeout(resolve, minInterval - timeSinceLastRequest)

504

);

505

}

506

507

lastRequestTime = Date.now();

508

return dispatch(opts, handler);

509

};

510

};

511

}

512

513

// Use custom interceptors

514

const agent = new Agent()

515

.compose(

516

authInterceptor('your-auth-token'),

517

rateLimitInterceptor(5), // 5 requests per second

518

customInterceptor({ option: 'value' })

519

);

520

```

521

522

## Complete Interceptor Chain Example

523

524

```javascript

525

import { Agent, interceptors, cacheStores } from 'undici';

526

527

// Create comprehensive HTTP client with all features

528

const httpClient = new Agent({

529

factory: (origin, opts) => {

530

return new Pool(origin, {

531

...opts,

532

connections: 10,

533

pipelining: 1

534

});

535

}

536

})

537

.compose(

538

// Request/response logging

539

interceptors.dump({

540

request: true,

541

response: true,

542

requestHeaders: true,

543

responseHeaders: true,

544

logger: (msg) => console.log(`[HTTP] ${msg}`)

545

}),

546

547

// DNS caching

548

interceptors.dns({

549

maxItems: 100,

550

maxTtl: 300000

551

}),

552

553

// HTTP caching

554

interceptors.cache({

555

store: new cacheStores.MemoryCacheStore(),

556

methods: ['GET', 'HEAD'],

557

cacheByDefault: 300

558

}),

559

560

// Automatic decompression

561

interceptors.decompress(),

562

563

// Automatic redirects

564

interceptors.redirect({

565

maxRedirections: 5

566

}),

567

568

// Retry with exponential backoff

569

interceptors.retry({

570

maxRetries: 3,

571

methods: ['GET', 'HEAD', 'OPTIONS'],

572

statusCodes: [408, 413, 429, 500, 502, 503, 504]

573

}),

574

575

// Enhanced error handling

576

interceptors.responseError({

577

throwOnError: true,

578

statusCodes: [400, 401, 403, 404, 500, 502, 503],

579

includeResponseBody: true

580

})

581

);

582

583

// All features work together automatically

584

const response = await httpClient.request({

585

origin: 'https://api.example.com',

586

path: '/data',

587

method: 'GET'

588

});

589

```