Skip to content

feat: New Interceptor API #3919

Open
Open
@PandaWorker

Description

How about making a more convenient implementation of the interceptors?

import type { Dispatcher, Request, Response } from 'undici';
import { Agent } from 'undici';

// new interceptors api
export type InterceptorChain = (
	request: Dispatcher.RequestOptions
) => Promise<Dispatcher.ResponseData>;
export type Interceptor = (next: InterceptorChain) => InterceptorChain;

// OR

export type InterceptorChainFetch = (
	request: Request
) => Promise<Response>;
export type InterceptorFetch = (next: InterceptorChain) => InterceptorChain;

export function composeInterceptors(interceptors: Interceptor[]) {
	return (original: InterceptorChain): InterceptorChain => {
		let chain: InterceptorChain = request => original(request);

		// create chain call:
		// fn3(req) -> fn2(req) -> fn1(req) ->
		//                                                          original
		// fn3(res) <- fn2(res) <- fn1(res) <-
		//
		for (let i = 0; i < interceptors.length; i++) {
			chain = interceptors[i](chain);
		}

		return chain;
	};
}

export function toDispatchComposeInterceptor(
	interceptor: Interceptor
): Dispatcher.DispatcherComposeInterceptor {
	return dispatch => {
		// TODO: implement
		const request = interceptor((request) => {});

		return (opts, handler) => {
			return dispatch(opts, handler);
		};
	};
}

const interceptor = composeInterceptors([loggerInterceptor()]);

const agent = new Agent().compose(toDispatchComposeInterceptor(interceptor));
const agent2 = new Agent().interceptors.add(interceptor);
const agent3 = new Agent().interceptors.clear();

function loggerInterceptor(): Interceptor {
	const log = console.log.bind(console);

	return next => async request => {
		const timeStart = Date.now();
		log('request:', request.method, request.path);

		const resp = await next(request);
		const timeEnd = Date.now();
		log(
			'resp:',
			request.method,
			request.path,
			resp.statusCode,
			(timeEnd - timeStart).toFixed(2)
		);

		return resp;
	};
}

function decompress(body, contentEncoding) {
	// TODO: decompress
	return body;
}

function decompressInterceptor(): Interceptor {
	return next => async request => {
		const resp = await next(request);

		if (request?.decompress === true && resp.body && resp.headers['content-encoding']) {
			resp.body = decompress(resp.body, resp.headers['content-encoding']);
		}

		return resp;
	};
}

In almost 99% of cases, we need the request/response object at once, and not in separate hooks.
I don't think we'll get a performance drawdown when adding such an api.

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions