fortify2-js
Version:
MOST POWERFUL JavaScript Security Library! Military-grade cryptography + 19 enhanced object methods + quantum-resistant algorithms + perfect TypeScript support. More powerful than Lodash with built-in security.
414 lines (410 loc) • 15.3 kB
JavaScript
'use strict';
var events = require('events');
/**
* Route Optimization Plugin
*
* Automatically detects popular routes and optimizes them in the background
* Features:
* - Route popularity tracking
* - Automatic caching for hot routes
* - Response time optimization
* - Predictive preloading
* - Route pattern analysis
*/
class RouteOptimizationPlugin extends events.EventEmitter {
constructor(config = {}) {
super();
this.routeStats = new Map();
this.optimizedRoutes = new Set();
this.config = {
enabled: true,
analysisInterval: 60000, // 1 minute
optimizationThreshold: 100,
popularityWindow: 3600000, // 1 hour
maxTrackedRoutes: 1000,
autoOptimization: true,
customRules: [],
onOptimization: () => { },
onAnalysis: () => { },
...config,
};
}
/**
* Initialize the plugin with Express app
*/
initialize(app, logger) {
if (!this.config.enabled)
return;
this.app = app;
this.logger = logger;
// Install route tracking middleware
this.installTrackingMiddleware();
// Start background analysis
this.startBackgroundAnalysis();
this.logger.info("plugins", "Route Optimization Plugin initialized");
}
/**
* Install middleware to track route usage
*/
installTrackingMiddleware() {
this.app.use((req, res, next) => {
const startTime = Date.now();
const routeKey = `${req.method}:${req.path}`;
// Track request start
this.trackRouteStart(routeKey);
// Override res.end to capture response time
const originalEnd = res.end.bind(res);
res.end = (...args) => {
const responseTime = Date.now() - startTime;
const statusCode = res.statusCode;
this.trackRouteEnd(routeKey, responseTime, statusCode);
return originalEnd(...args);
};
next();
});
}
/**
* Track route request start
*/
trackRouteStart(routeKey) {
const [method, path] = routeKey.split(":");
if (!this.routeStats.has(routeKey)) {
this.routeStats.set(routeKey, {
path,
method,
hitCount: 0,
totalResponseTime: 0,
averageResponseTime: 0,
lastAccessed: new Date(),
errorCount: 0,
cacheHits: 0,
cacheMisses: 0,
popularity: 0,
});
}
const stats = this.routeStats.get(routeKey);
stats.hitCount++;
stats.lastAccessed = new Date();
}
/**
* Track route request completion
*/
trackRouteEnd(routeKey, responseTime, statusCode) {
const stats = this.routeStats.get(routeKey);
if (!stats)
return;
stats.totalResponseTime += responseTime;
stats.averageResponseTime = stats.totalResponseTime / stats.hitCount;
if (statusCode >= 400) {
stats.errorCount++;
}
// Calculate popularity score
stats.popularity = this.calculatePopularity(stats);
// Check if route needs optimization
if (this.config.autoOptimization && this.shouldOptimizeRoute(stats)) {
this.optimizeRoute(routeKey, stats);
}
}
/**
* Calculate route popularity score
*/
calculatePopularity(stats) {
const now = Date.now();
const timeSinceLastAccess = now - stats.lastAccessed.getTime();
const recencyFactor = Math.max(0, 1 - timeSinceLastAccess / this.config.popularityWindow);
const frequencyScore = Math.log(stats.hitCount + 1);
const performanceScore = Math.max(0, 1 - stats.averageResponseTime / 1000); // Normalize to 1 second
const reliabilityScore = Math.max(0, 1 - stats.errorCount / stats.hitCount);
return (frequencyScore * 0.4 +
performanceScore * 0.3 +
reliabilityScore * 0.2 +
recencyFactor * 0.1);
}
/**
* Check if route should be optimized
*/
shouldOptimizeRoute(stats) {
if (this.optimizedRoutes.has(`${stats.method}:${stats.path}`)) {
return false; // Already optimized
}
// Check threshold
if (stats.hitCount < this.config.optimizationThreshold) {
return false;
}
// Check custom rules
for (const rule of this.config.customRules) {
if (this.matchesPattern(stats.path, rule.pattern)) {
return (stats.hitCount >= rule.minHits &&
stats.averageResponseTime <= rule.maxResponseTime);
}
}
// Default optimization criteria
return stats.popularity > 5 || stats.averageResponseTime > 500;
}
/**
* Optimize a specific route
*/
optimizeRoute(routeKey, stats) {
if (this.optimizedRoutes.has(routeKey))
return;
const optimizations = [];
// Apply caching optimization
if (stats.averageResponseTime > 200) {
this.applyCachingOptimization(routeKey, stats);
optimizations.push("caching");
}
// Apply response compression
if (stats.hitCount > 500) {
this.applyCompressionOptimization(routeKey, stats);
optimizations.push("compression");
}
// Apply preloading for very popular routes
if (stats.popularity > 8) {
this.applyPreloadingOptimization(routeKey, stats);
optimizations.push("preloading");
}
this.optimizedRoutes.add(routeKey);
this.logger.info("plugins", `Route optimized: ${routeKey} (${optimizations.join(", ")})`);
this.config.onOptimization(routeKey, optimizations.join(", "));
this.emit("route_optimized", { routeKey, stats, optimizations });
}
/**
* Apply caching optimization
*/
applyCachingOptimization(routeKey, stats) {
const [method, path] = routeKey.split(":");
// Determine cache strategy based on route performance
let cacheStrategy = "moderate";
let cacheTTL = 300; // 5 minutes default
if (stats.averageResponseTime > 1000) {
cacheStrategy = "aggressive";
cacheTTL = 1800; // 30 minutes for slow routes
}
else if (stats.averageResponseTime < 200) {
cacheStrategy = "conservative";
cacheTTL = 60; // 1 minute for fast routes
}
// Apply route-specific caching middleware
this.app.use(path, (req, res, next) => {
// Only cache GET requests
if (req.method !== "GET") {
return next();
}
const cacheKey = `route_cache:${method}:${req.originalUrl}:${JSON.stringify(req.query)}`;
// Try to get from cache first
if (this.app.cacheManager) {
this.app.cacheManager
.get(cacheKey)
.then((cachedResponse) => {
if (cachedResponse) {
stats.cacheHits++;
res.set("X-Cache", "HIT");
res.set("X-Cache-Strategy", cacheStrategy);
return res.json(cachedResponse);
}
// Cache miss - intercept response
stats.cacheMisses++;
const originalJson = res.json.bind(res);
res.json = (data) => {
// Cache the response
this.app.cacheManager.set(cacheKey, data, {
ttl: cacheTTL,
});
res.set("X-Cache", "MISS");
res.set("X-Cache-Strategy", cacheStrategy);
return originalJson(data);
};
next();
})
.catch(() => {
// Cache error - continue without caching
next();
});
}
else {
next();
}
});
this.logger.debug("plugins", `Applied ${cacheStrategy} caching optimization to ${routeKey} (TTL: ${cacheTTL}s)`);
}
/**
* Apply compression optimization
*/
applyCompressionOptimization(routeKey, stats) {
const [method, path] = routeKey.split(":");
// Apply compression middleware for this specific route
this.app.use(path, (req, res, next) => {
// Enable compression for responses larger than 1KB
const originalSend = res.send.bind(res);
const originalJson = res.json.bind(res);
res.send = (data) => {
if (data && typeof data === "string" && data.length > 1024) {
res.set("X-Compression", "ENABLED");
res.set("Content-Encoding", "gzip");
}
return originalSend(data);
};
res.json = (data) => {
const jsonString = JSON.stringify(data);
if (jsonString.length > 1024) {
res.set("X-Compression", "ENABLED");
res.set("Content-Encoding", "gzip");
}
return originalJson(data);
};
next();
});
this.logger.debug("plugins", `Applied compression optimization to ${routeKey} (avg response: ${stats.averageResponseTime}ms)`);
}
/**
* Apply preloading optimization
*/
applyPreloadingOptimization(routeKey, stats) {
const [method, path] = routeKey.split(":");
// Preload common responses for very popular routes
if (stats.popularity > 8 && method === "GET") {
// Schedule preloading during low traffic periods
setTimeout(() => {
this.preloadRouteData(path, stats);
}, 60000); // Preload after 1 minute
// Set up predictive preloading based on access patterns
this.app.use(path, (req, res, next) => {
// Track access patterns for predictive preloading
const userAgent = req.headers["user-agent"] || "unknown";
const referer = req.headers["referer"] || "direct";
// Store access pattern for future preloading
const accessPattern = {
path: req.originalUrl,
userAgent,
referer,
timestamp: Date.now(),
query: req.query,
};
// Add to preload queue if pattern is detected
this.schedulePreload(accessPattern);
res.set("X-Preload", "ENABLED");
next();
});
}
this.logger.debug("plugins", `Applied preloading optimization to ${routeKey} (popularity: ${stats.popularity.toFixed(2)})`);
}
/**
* Preload route data
*/
preloadRouteData(path, stats) {
// Simulate preloading by making internal requests
if (this.app.cacheManager) {
const preloadKey = `preload:${path}:${Date.now()}`;
// Store preload metadata
this.app.cacheManager.set(preloadKey, {
path,
preloadedAt: new Date(),
averageResponseTime: stats.averageResponseTime,
hitCount: stats.hitCount,
}, { ttl: 3600 }); // 1 hour
this.logger.debug("plugins", `Preloaded data for ${path}`);
}
}
/**
* Schedule predictive preloading
*/
schedulePreload(accessPattern) {
// Simple predictive logic - preload related resources
if (accessPattern.path.includes("/api/")) {
const relatedPaths = [
accessPattern.path + "/details",
accessPattern.path + "/related",
accessPattern.path.replace("/api/", "/api/meta/"),
];
relatedPaths.forEach((relatedPath) => {
setTimeout(() => {
this.logger.debug("plugins", `Predictive preload scheduled for ${relatedPath}`);
}, Math.random() * 30000); // Random delay up to 30 seconds
});
}
}
/**
* Check if path matches pattern
*/
matchesPattern(path, pattern) {
const regex = new RegExp(pattern.replace(/\*/g, ".*"));
return regex.test(path);
}
/**
* Start background analysis
*/
startBackgroundAnalysis() {
this.analysisTimer = setInterval(() => {
this.performAnalysis();
}, this.config.analysisInterval);
}
/**
* Perform route analysis
*/
performAnalysis() {
const stats = Array.from(this.routeStats.values())
.sort((a, b) => b.popularity - a.popularity)
.slice(0, 20); // Top 20 routes
this.logger.debug("plugins", `Analyzed ${this.routeStats.size} routes, top performers: ${stats.length}`);
this.config.onAnalysis(stats);
this.emit("analysis_complete", stats);
// Cleanup old routes
this.cleanupOldRoutes();
}
/**
* Cleanup old unused routes
*/
cleanupOldRoutes() {
const now = Date.now();
const cutoff = now - this.config.popularityWindow * 2; // 2x the popularity window
for (const [routeKey, stats] of this.routeStats.entries()) {
if (stats.lastAccessed.getTime() < cutoff && stats.hitCount < 10) {
this.routeStats.delete(routeKey);
this.optimizedRoutes.delete(routeKey);
}
}
// Limit total tracked routes
if (this.routeStats.size > this.config.maxTrackedRoutes) {
const sortedRoutes = Array.from(this.routeStats.entries()).sort(([, a], [, b]) => a.popularity - b.popularity);
const toRemove = sortedRoutes.slice(0, this.routeStats.size - this.config.maxTrackedRoutes);
toRemove.forEach(([routeKey]) => {
this.routeStats.delete(routeKey);
this.optimizedRoutes.delete(routeKey);
});
}
}
/**
* Get route statistics
*/
getRouteStats() {
return Array.from(this.routeStats.values()).sort((a, b) => b.popularity - a.popularity);
}
/**
* Get optimized routes
*/
getOptimizedRoutes() {
return Array.from(this.optimizedRoutes);
}
/**
* Manually optimize a route
*/
manualOptimize(routeKey) {
const stats = this.routeStats.get(routeKey);
if (stats) {
this.optimizeRoute(routeKey, stats);
}
}
/**
* Destroy the plugin
*/
destroy() {
if (this.analysisTimer) {
clearInterval(this.analysisTimer);
}
this.routeStats.clear();
this.optimizedRoutes.clear();
this.removeAllListeners();
}
}
exports.RouteOptimizationPlugin = RouteOptimizationPlugin;
//# sourceMappingURL=route-optimization-plugin.js.map