Request/response interception framework for implementing cross-cutting concerns like logging, authentication, metrics, and error handling on both client and server sides with flexible composition patterns.
Client-side interception system for modifying outgoing requests and incoming responses.
/**
* Client interceptor function interface
* @param options - Interceptor configuration options
* @param nextCall - Function to continue the interceptor chain
* @returns InterceptingCall instance for handling the call
*/
interface Interceptor {
(options: InterceptorOptions, nextCall: NextCall): InterceptingCall;
}
/**
* Options passed to client interceptors
*/
interface InterceptorOptions {
/** The method being called */
method_definition: MethodDefinition<any, any>;
}
/**
* Function to continue the interceptor chain
*/
interface NextCall {
(options: InterceptorOptions): InterceptingCall;
}
/**
* Intercepting call interface for handling requests and responses
*/
interface InterceptingCall {
/** Start the call with metadata and listener */
start(metadata: Metadata, listener: InterceptingListener, next: Function): void;
/** Send a message (for streaming calls) */
sendMessage(message: any, next: Function): void;
/** Half-close the call (signal end of client messages) */
halfClose(next: Function): void;
/** Cancel the call */
cancel(next: Function): void;
}Dynamic interceptor creation for context-dependent interception.
/**
* Provider for creating interceptors dynamically
*/
interface InterceptorProvider {
(methodDefinition: MethodDefinition<any, any>): Interceptor;
}
/**
* Error thrown when interceptor configuration is invalid
*/
class InterceptorConfigurationError extends Error {
constructor(message: string);
}Helper classes for building complex interceptors with fluent API.
/**
* Builder for request interceptors
*/
class RequesterBuilder {
/** Build a requester function */
build(): Requester;
}
/**
* Builder for response listeners
*/
class ListenerBuilder {
/** Build a listener function */
build(): InterceptingListener;
}
/**
* Request interceptor interface
*/
interface Requester {
/** Start the request */
start(metadata: Metadata, listener: InterceptingListener, next: Function): void;
/** Send message */
sendMessage(message: any, next: Function): void;
/** Half close */
halfClose(next: Function): void;
/** Cancel */
cancel(next: Function): void;
}Server-side interception system for processing incoming requests and outgoing responses.
/**
* Server interceptor function interface
* @param methodDescriptor - Description of the method being called
* @param call - The incoming server call
* @returns Modified or wrapped server intercepting call
*/
interface ServerInterceptor {
<ReqType, RespType>(
methodDescriptor: ServerMethodDefinition<ReqType, RespType>,
call: ServerInterceptingCallInterface<ReqType>
): ServerInterceptingCall<ReqType, RespType>;
}Interfaces for server-side call interception and response handling.
/**
* Server intercepting call interface
*/
interface ServerInterceptingCallInterface<RequestType> {
/** Request metadata */
metadata: Metadata;
/** Get peer address */
getPeer(): string;
/** Send initial metadata */
sendMetadata(responseMetadata: Metadata): void;
/** Check if cancelled */
isCancelled(): boolean;
}
/**
* Server intercepting call implementation
*/
interface ServerInterceptingCall<RequestType, ResponseType>
extends ServerInterceptingCallInterface<RequestType> {
/** Request data (for unary calls) */
request?: RequestType;
}Components for handling server-side request processing and response generation.
/**
* Server listener interface for processing requests
*/
interface ServerListener {
/** Called when metadata is received */
onReceiveMetadata(metadata: Metadata): void;
/** Called when a message is received */
onReceiveMessage(message: any): void;
/** Called when client half-closes */
onReceiveHalfClose(): void;
/** Called when call is cancelled */
onCancel(): void;
}
/**
* Full server listener with additional events
*/
interface FullServerListener extends ServerListener {
/** Called when call starts */
onReceiveCall(call: any): void;
}
/**
* Server responder interface for sending responses
*/
interface Responder {
/** Send initial metadata */
sendMetadata(metadata: Metadata): void;
/** Send a message */
sendMessage(message: any): void;
/** Send status and close */
sendStatus(status: StatusObject): void;
}
/**
* Full server responder with additional capabilities
*/
interface FullResponder extends Responder {
/** Start the response */
start(metadata: Metadata): void;
}
/**
* Builder for server listeners
*/
class ServerListenerBuilder {
/** Build a listener */
build(): ServerListener;
}
/**
* Builder for server responders
*/
class ResponderBuilder {
/** Build a responder */
build(): Responder;
}Core interfaces for call listeners and intercepting listeners.
/**
* Basic call listener interface
*/
interface Listener {
/** Called when metadata is received */
onReceiveMetadata(metadata: Metadata): void;
/** Called when a message is received */
onReceiveMessage(message: any): void;
/** Called when status is received */
onReceiveStatus(status: StatusObject): void;
}
/**
* Intercepting listener interface with additional events
*/
interface InterceptingListener extends Listener {
/** Called when initial metadata is received */
onReceiveInitialMetadata(metadata: Metadata): void;
/** Called when trailing metadata is received */
onReceiveTrailingMetadata(metadata: Metadata): void;
}import { Interceptor, Metadata } from "@grpc/grpc-js";
const authInterceptor: Interceptor = (options, nextCall) => {
return new InterceptingCall(nextCall(options), {
start(metadata, listener, next) {
// Add authentication to all outgoing calls
metadata.set('authorization', `Bearer ${getAuthToken()}`);
metadata.set('x-request-id', generateRequestId());
next(metadata, listener);
}
});
};
// Use with client
const client = new ServiceClient(address, credentials.createInsecure(), {
interceptors: [authInterceptor]
});import { Interceptor, InterceptingCall } from "@grpc/grpc-js";
const loggingInterceptor: Interceptor = (options, nextCall) => {
const methodName = options.method_definition.path;
const startTime = Date.now();
return new InterceptingCall(nextCall(options), {
start(metadata, listener, next) {
console.log(`[${methodName}] Call started`);
console.log(`[${methodName}] Metadata:`, metadata.getMap());
const wrappedListener = {
onReceiveInitialMetadata(metadata) {
console.log(`[${methodName}] Received initial metadata`);
listener.onReceiveInitialMetadata(metadata);
},
onReceiveMessage(message) {
console.log(`[${methodName}] Received message:`, message);
listener.onReceiveMessage(message);
},
onReceiveStatus(status) {
const duration = Date.now() - startTime;
console.log(`[${methodName}] Call completed in ${duration}ms with status:`, status.code);
listener.onReceiveStatus(status);
}
};
next(metadata, wrappedListener);
},
sendMessage(message, next) {
console.log(`[${methodName}] Sending message:`, message);
next(message);
}
});
};import { Interceptor, status } from "@grpc/grpc-js";
const retryInterceptor: Interceptor = (options, nextCall) => {
const maxRetries = 3;
let attemptCount = 0;
const makeCall = (): InterceptingCall => {
attemptCount++;
return new InterceptingCall(nextCall(options), {
start(metadata, listener, next) {
const wrappedListener = {
...listener,
onReceiveStatus(statusObj) {
if (shouldRetry(statusObj.code) && attemptCount < maxRetries) {
console.log(`Retrying call, attempt ${attemptCount + 1}`);
// Start a new call
const retryCall = makeCall();
retryCall.start(metadata, listener, () => {});
return;
}
listener.onReceiveStatus(statusObj);
}
};
next(metadata, wrappedListener);
}
});
};
return makeCall();
};
function shouldRetry(statusCode: status): boolean {
return statusCode === status.UNAVAILABLE ||
statusCode === status.DEADLINE_EXCEEDED ||
statusCode === status.RESOURCE_EXHAUSTED;
}import { ServerInterceptor, status, Metadata } from "@grpc/grpc-js";
const authServerInterceptor: ServerInterceptor = (methodDescriptor, call) => {
const metadata = call.metadata;
const authHeader = metadata.get('authorization')[0] as string;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
// Send authentication error
const errorMetadata = new Metadata();
errorMetadata.set('www-authenticate', 'Bearer');
throw {
code: status.UNAUTHENTICATED,
details: 'Missing or invalid authentication token',
metadata: errorMetadata
};
}
const token = authHeader.substring(7);
if (!validateToken(token)) {
throw {
code: status.PERMISSION_DENIED,
details: 'Invalid authentication token'
};
}
// Add user info to metadata for handlers
metadata.set('x-user-id', getUserIdFromToken(token));
return call; // Continue with authenticated call
};
function validateToken(token: string): boolean {
// Token validation logic
return token.length > 0;
}
function getUserIdFromToken(token: string): string {
// Extract user ID from token
return 'user123';
}import { ServerInterceptor } from "@grpc/grpc-js";
const requestLoggingInterceptor: ServerInterceptor = (methodDescriptor, call) => {
const startTime = Date.now();
const methodName = methodDescriptor.path;
const clientAddress = call.getPeer();
const requestId = call.metadata.get('x-request-id')[0] || 'unknown';
console.log(`[${requestId}] ${methodName} started from ${clientAddress}`);
console.log(`[${requestId}] Request metadata:`, call.metadata.getMap());
// Wrap the call to log completion
const originalCall = call;
return new Proxy(call, {
get(target, prop) {
if (prop === 'sendMetadata') {
return (metadata: Metadata) => {
console.log(`[${requestId}] Sending initial metadata`);
return originalCall.sendMetadata(metadata);
};
}
return target[prop];
}
});
};import { Interceptor, ServerInterceptor } from "@grpc/grpc-js";
// Client interceptors (applied in order)
const clientInterceptors: Interceptor[] = [
authInterceptor,
loggingInterceptor,
retryInterceptor,
metricsInterceptor
];
// Server interceptors (applied in reverse order)
const serverInterceptors: ServerInterceptor[] = [
requestLoggingInterceptor,
authServerInterceptor,
rateLimitingInterceptor,
metricsServerInterceptor
];
// Client with interceptors
const client = new ServiceClient(address, credentials.createInsecure(), {
interceptors: clientInterceptors
});
// Server with interceptors
const server = new Server({
interceptors: serverInterceptors
});import { InterceptorProvider, MethodDefinition } from "@grpc/grpc-js";
const conditionalInterceptorProvider: InterceptorProvider = (methodDefinition) => {
// Return different interceptors based on method
if (methodDefinition.path.includes('Admin')) {
return adminAuthInterceptor;
} else if (methodDefinition.path.includes('Public')) {
return publicLoggingInterceptor;
} else {
return standardInterceptor;
}
};
// Use provider with client
const client = new ServiceClient(address, credentials.createInsecure(), {
interceptor_providers: [conditionalInterceptorProvider]
});import { Interceptor } from "@grpc/grpc-js";
class CircuitBreakerInterceptor {
private failureCount = 0;
private lastFailureTime = 0;
private readonly maxFailures = 5;
private readonly timeout = 60000; // 1 minute
createInterceptor(): Interceptor {
return (options, nextCall) => {
// Check circuit breaker state
if (this.isCircuitOpen()) {
return new InterceptingCall(nextCall(options), {
start(metadata, listener, next) {
listener.onReceiveStatus({
code: status.UNAVAILABLE,
details: 'Circuit breaker is open',
metadata: new Metadata()
});
}
});
}
return new InterceptingCall(nextCall(options), {
start: (metadata, listener, next) => {
const wrappedListener = {
...listener,
onReceiveStatus: (statusObj) => {
if (statusObj.code !== status.OK) {
this.recordFailure();
} else {
this.recordSuccess();
}
listener.onReceiveStatus(statusObj);
}
};
next(metadata, wrappedListener);
}
});
};
}
private isCircuitOpen(): boolean {
const now = Date.now();
return this.failureCount >= this.maxFailures &&
(now - this.lastFailureTime) < this.timeout;
}
private recordFailure(): void {
this.failureCount++;
this.lastFailureTime = Date.now();
}
private recordSuccess(): void {
this.failureCount = 0;
}
}
const circuitBreaker = new CircuitBreakerInterceptor();
const client = new ServiceClient(address, credentials.createInsecure(), {
interceptors: [circuitBreaker.createInterceptor()]
});