fix(routing): unify route based architecture
This commit is contained in:
259
ts/proxies/network-proxy/function-cache.ts
Normal file
259
ts/proxies/network-proxy/function-cache.ts
Normal file
@@ -0,0 +1,259 @@
|
||||
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');
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user