tressi
Version:
A lightweight, declarative stress testing CLI for modern developers.
133 lines (114 loc) • 3.44 kB
text/typescript
import { Agent, Dispatcher } from 'undici';
export interface AgentConfig {
connections?: number;
keepAliveTimeout?: number;
keepAliveMaxTimeout?: number;
headersTimeout?: number;
bodyTimeout?: number;
}
export interface EndpointAgent {
url: string;
agent: Dispatcher;
}
export class HttpAgentManager {
private agents = new Map<string, Dispatcher>();
private defaultConfig: AgentConfig;
constructor(defaultConfig: AgentConfig = {}) {
this.defaultConfig = {
connections: 1024,
keepAliveTimeout: 4000,
keepAliveMaxTimeout: 60000,
headersTimeout: 30000,
bodyTimeout: 30000,
...defaultConfig,
};
}
/**
* Get or create an agent for a specific endpoint
* @param endpointUrl The endpoint URL
* @param config Optional configuration override for this endpoint
* @returns Dispatcher instance for the endpoint
*/
getAgent(endpointUrl: string, config?: AgentConfig): Dispatcher {
// In test environment, use global dispatcher (which can be mocked)
if (process.env.NODE_ENV === 'test') {
return undefined as never; // Let undici use global dispatcher
}
const key = this.getEndpointKey(endpointUrl);
if (!this.agents.has(key)) {
const mergedConfig = { ...this.defaultConfig, ...config };
const agent = new Agent({
connections: mergedConfig.connections,
keepAliveTimeout: mergedConfig.keepAliveTimeout,
keepAliveMaxTimeout: mergedConfig.keepAliveMaxTimeout,
headersTimeout: mergedConfig.headersTimeout,
bodyTimeout: mergedConfig.bodyTimeout,
});
this.agents.set(key, agent);
}
return this.agents.get(key)!;
}
/**
* Remove an agent for a specific endpoint
* @param endpointUrl The endpoint URL
*/
removeAgent(endpointUrl: string): boolean {
const key = this.getEndpointKey(endpointUrl);
const agent = this.agents.get(key);
if (agent) {
// Close the agent gracefully
if ('close' in agent && typeof agent.close === 'function') {
agent.close();
}
return this.agents.delete(key);
}
return false;
}
/**
* Get all active agents
* @returns Array of endpoint-agent pairs
*/
getAllAgents(): EndpointAgent[] {
return Array.from(this.agents.entries()).map(([url, agent]) => ({
url,
agent,
}));
}
/**
* Close all agents and clear the cache
*/
closeAll(): void {
for (const [, agent] of this.agents.entries()) {
if ('close' in agent && typeof agent.close === 'function') {
agent.close();
}
}
this.agents.clear();
}
/**
* Get the number of active agents
*/
getAgentCount(): number {
return this.agents.size;
}
/**
* Generate a consistent key for an endpoint URL
* @param endpointUrl The endpoint URL
* @returns Normalized key for the endpoint
*/
private getEndpointKey(endpointUrl: string): string {
try {
const url = new URL(endpointUrl);
// Use origin (protocol + host + port) as the key to group similar endpoints
return url.origin;
} catch {
// Fallback to the raw URL if parsing fails
return endpointUrl;
}
}
}
// Global instance for convenience
export const globalAgentManager = new HttpAgentManager();
// Export for testing
export const createAgentManager = (config?: AgentConfig): HttpAgentManager =>
new HttpAgentManager(config);