259 lines
7.4 KiB
TypeScript
259 lines
7.4 KiB
TypeScript
import type { IRouteContext } from '../../core/models/route-context.js';
|
|
import type { ILogger } from './models/types.js';
|
|
|
|
/**
|
|
* Interface for cached function result
|
|
*/
|
|
interface ICachedResult<T> {
|
|
value: T;
|
|
expiry: number;
|
|
hash: string;
|
|
}
|
|
|
|
/**
|
|
* Function cache for NetworkProxy function-based targets
|
|
*
|
|
* This cache improves performance for function-based targets by storing
|
|
* the results of function evaluations and reusing them for similar contexts.
|
|
*/
|
|
export class FunctionCache {
|
|
// Cache storage
|
|
private hostCache: Map<string, ICachedResult<string | string[]>> = new Map();
|
|
private portCache: Map<string, ICachedResult<number>> = new Map();
|
|
|
|
// Maximum number of entries to store in each cache
|
|
private maxCacheSize: number;
|
|
|
|
// Default TTL for cache entries in milliseconds (default: 5 seconds)
|
|
private defaultTtl: number;
|
|
|
|
// Logger
|
|
private logger: ILogger;
|
|
|
|
/**
|
|
* Creates a new function cache
|
|
*
|
|
* @param logger Logger for debug output
|
|
* @param options Cache options
|
|
*/
|
|
constructor(
|
|
logger: ILogger,
|
|
options: {
|
|
maxCacheSize?: number;
|
|
defaultTtl?: number;
|
|
} = {}
|
|
) {
|
|
this.logger = logger;
|
|
this.maxCacheSize = options.maxCacheSize || 1000;
|
|
this.defaultTtl = options.defaultTtl || 5000; // 5 seconds default
|
|
|
|
// Start the cache cleanup timer
|
|
setInterval(() => this.cleanupCache(), 30000); // Cleanup every 30 seconds
|
|
}
|
|
|
|
/**
|
|
* Compute a hash for a context object
|
|
* This is used to identify similar contexts for caching
|
|
*
|
|
* @param context The route context to hash
|
|
* @param functionId Identifier for the function (usually route name or ID)
|
|
* @returns A string hash
|
|
*/
|
|
private computeContextHash(context: IRouteContext, functionId: string): string {
|
|
// Extract relevant properties for the hash
|
|
const hashBase = {
|
|
functionId,
|
|
port: context.port,
|
|
domain: context.domain,
|
|
clientIp: context.clientIp,
|
|
path: context.path,
|
|
query: context.query,
|
|
isTls: context.isTls,
|
|
tlsVersion: context.tlsVersion
|
|
};
|
|
|
|
// Generate a hash string
|
|
return JSON.stringify(hashBase);
|
|
}
|
|
|
|
/**
|
|
* Get cached host result for a function and context
|
|
*
|
|
* @param context Route context
|
|
* @param functionId Identifier for the function
|
|
* @returns Cached host value or undefined if not found
|
|
*/
|
|
public getCachedHost(context: IRouteContext, functionId: string): string | string[] | undefined {
|
|
const hash = this.computeContextHash(context, functionId);
|
|
const cached = this.hostCache.get(hash);
|
|
|
|
// Return if no cached value or expired
|
|
if (!cached || cached.expiry < Date.now()) {
|
|
if (cached) {
|
|
// If expired, remove from cache
|
|
this.hostCache.delete(hash);
|
|
this.logger.debug(`Cache miss (expired) for host function: ${functionId}`);
|
|
} else {
|
|
this.logger.debug(`Cache miss for host function: ${functionId}`);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
this.logger.debug(`Cache hit for host function: ${functionId}`);
|
|
return cached.value;
|
|
}
|
|
|
|
/**
|
|
* Get cached port result for a function and context
|
|
*
|
|
* @param context Route context
|
|
* @param functionId Identifier for the function
|
|
* @returns Cached port value or undefined if not found
|
|
*/
|
|
public getCachedPort(context: IRouteContext, functionId: string): number | undefined {
|
|
const hash = this.computeContextHash(context, functionId);
|
|
const cached = this.portCache.get(hash);
|
|
|
|
// Return if no cached value or expired
|
|
if (!cached || cached.expiry < Date.now()) {
|
|
if (cached) {
|
|
// If expired, remove from cache
|
|
this.portCache.delete(hash);
|
|
this.logger.debug(`Cache miss (expired) for port function: ${functionId}`);
|
|
} else {
|
|
this.logger.debug(`Cache miss for port function: ${functionId}`);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
this.logger.debug(`Cache hit for port function: ${functionId}`);
|
|
return cached.value;
|
|
}
|
|
|
|
/**
|
|
* Store a host function result in the cache
|
|
*
|
|
* @param context Route context
|
|
* @param functionId Identifier for the function
|
|
* @param value Host value to cache
|
|
* @param ttl Optional TTL in milliseconds
|
|
*/
|
|
public cacheHost(
|
|
context: IRouteContext,
|
|
functionId: string,
|
|
value: string | string[],
|
|
ttl?: number
|
|
): void {
|
|
const hash = this.computeContextHash(context, functionId);
|
|
const expiry = Date.now() + (ttl || this.defaultTtl);
|
|
|
|
// Check if we need to prune the cache before adding
|
|
if (this.hostCache.size >= this.maxCacheSize) {
|
|
this.pruneOldestEntries(this.hostCache);
|
|
}
|
|
|
|
// Store the result
|
|
this.hostCache.set(hash, { value, expiry, hash });
|
|
this.logger.debug(`Cached host function result for: ${functionId}`);
|
|
}
|
|
|
|
/**
|
|
* Store a port function result in the cache
|
|
*
|
|
* @param context Route context
|
|
* @param functionId Identifier for the function
|
|
* @param value Port value to cache
|
|
* @param ttl Optional TTL in milliseconds
|
|
*/
|
|
public cachePort(
|
|
context: IRouteContext,
|
|
functionId: string,
|
|
value: number,
|
|
ttl?: number
|
|
): void {
|
|
const hash = this.computeContextHash(context, functionId);
|
|
const expiry = Date.now() + (ttl || this.defaultTtl);
|
|
|
|
// Check if we need to prune the cache before adding
|
|
if (this.portCache.size >= this.maxCacheSize) {
|
|
this.pruneOldestEntries(this.portCache);
|
|
}
|
|
|
|
// Store the result
|
|
this.portCache.set(hash, { value, expiry, hash });
|
|
this.logger.debug(`Cached port function result for: ${functionId}`);
|
|
}
|
|
|
|
/**
|
|
* Remove expired entries from the cache
|
|
*/
|
|
private cleanupCache(): void {
|
|
const now = Date.now();
|
|
let expiredCount = 0;
|
|
|
|
// Clean up host cache
|
|
for (const [hash, cached] of this.hostCache.entries()) {
|
|
if (cached.expiry < now) {
|
|
this.hostCache.delete(hash);
|
|
expiredCount++;
|
|
}
|
|
}
|
|
|
|
// Clean up port cache
|
|
for (const [hash, cached] of this.portCache.entries()) {
|
|
if (cached.expiry < now) {
|
|
this.portCache.delete(hash);
|
|
expiredCount++;
|
|
}
|
|
}
|
|
|
|
if (expiredCount > 0) {
|
|
this.logger.debug(`Cleaned up ${expiredCount} expired cache entries`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prune oldest entries from a cache map
|
|
* Used when the cache exceeds the maximum size
|
|
*
|
|
* @param cache The cache map to prune
|
|
*/
|
|
private pruneOldestEntries<T>(cache: Map<string, ICachedResult<T>>): void {
|
|
// Find the oldest entries
|
|
const now = Date.now();
|
|
const itemsToRemove = Math.floor(this.maxCacheSize * 0.2); // Remove 20% of the cache
|
|
|
|
// Convert to array for sorting
|
|
const entries = Array.from(cache.entries());
|
|
|
|
// Sort by expiry (oldest first)
|
|
entries.sort((a, b) => a[1].expiry - b[1].expiry);
|
|
|
|
// Remove oldest entries
|
|
const toRemove = entries.slice(0, itemsToRemove);
|
|
for (const [hash] of toRemove) {
|
|
cache.delete(hash);
|
|
}
|
|
|
|
this.logger.debug(`Pruned ${toRemove.length} oldest cache entries`);
|
|
}
|
|
|
|
/**
|
|
* Get current cache stats
|
|
*/
|
|
public getStats(): { hostCacheSize: number; portCacheSize: number } {
|
|
return {
|
|
hostCacheSize: this.hostCache.size,
|
|
portCacheSize: this.portCache.size
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Clear all cached entries
|
|
*/
|
|
public clearCache(): void {
|
|
this.hostCache.clear();
|
|
this.portCache.clear();
|
|
this.logger.info('Function cache cleared');
|
|
}
|
|
} |