@push.rocks/smartproxy
Version:
A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.
205 lines • 14.8 kB
JavaScript
import * as plugins from '../../plugins.js';
import { DomainMatcher, PathMatcher } from '../../core/routing/matchers/index.js';
/**
* Unified HTTP Router for reverse proxy requests
*
* Domain matching patterns:
* - Exact matches: "example.com"
* - Wildcard subdomains: "*.example.com" (matches any subdomain of example.com)
* - TLD wildcards: "example.*" (matches example.com, example.org, etc.)
* - Complex wildcards: "*.lossless*" (matches any subdomain of any lossless domain)
* - Default fallback: "*" (matches any unmatched domain)
*
* Path pattern matching:
* - Exact path: "/api/users"
* - Wildcard paths: "/api/*"
* - Path parameters: "/users/:id/profile"
*/
export class HttpRouter {
constructor(routes, logger) {
// Store routes sorted by priority
this.routes = [];
this.logger = logger || {
error: console.error.bind(console),
warn: console.warn.bind(console),
info: console.info.bind(console),
debug: console.debug?.bind(console)
};
if (routes) {
this.setRoutes(routes);
}
}
/**
* Sets a new set of routes
* @param routes Array of route configurations
*/
setRoutes(routes) {
this.routes = [...routes];
// Sort routes by priority (higher priority first)
this.routes.sort((a, b) => {
const priorityA = a.priority ?? 0;
const priorityB = b.priority ?? 0;
return priorityB - priorityA;
});
// Find default route if any (route with "*" as domain)
this.defaultRoute = this.routes.find(route => {
const domains = Array.isArray(route.match.domains)
? route.match.domains
: route.match.domains ? [route.match.domains] : [];
return domains.includes('*');
});
const uniqueDomains = this.getHostnames();
this.logger.info(`HttpRouter initialized with ${this.routes.length} routes (${uniqueDomains.length} unique hosts)`);
}
/**
* Routes a request based on hostname and path
* @param req The incoming HTTP request
* @returns The matching route or undefined if no match found
*/
routeReq(req) {
const result = this.routeReqWithDetails(req);
return result ? result.route : undefined;
}
/**
* Routes a request with detailed matching information
* @param req The incoming HTTP request
* @returns Detailed routing result including matched route and path information
*/
routeReqWithDetails(req) {
// Extract and validate host header
const originalHost = req.headers.host;
if (!originalHost) {
this.logger.error('No host header found in request');
return this.defaultRoute ? { route: this.defaultRoute } : undefined;
}
// Parse URL for path matching
const parsedUrl = plugins.url.parse(req.url || '/');
const urlPath = parsedUrl.pathname || '/';
// Extract hostname without port
const hostWithoutPort = originalHost.split(':')[0].toLowerCase();
// Find matching route
const matchingRoute = this.findMatchingRoute(hostWithoutPort, urlPath);
if (matchingRoute) {
return matchingRoute;
}
// Fall back to default route if available
if (this.defaultRoute) {
this.logger.warn(`No specific route found for host: ${hostWithoutPort}, using default`);
return { route: this.defaultRoute };
}
this.logger.error(`No route found for host: ${hostWithoutPort}`);
return undefined;
}
/**
* Find the best matching route for a given hostname and path
*/
findMatchingRoute(hostname, path) {
// Try each route in priority order
for (const route of this.routes) {
// Skip disabled routes
if (route.enabled === false) {
continue;
}
// Check domain match
if (route.match.domains) {
const domains = Array.isArray(route.match.domains)
? route.match.domains
: [route.match.domains];
// Check if any domain pattern matches
const domainMatches = domains.some(domain => DomainMatcher.match(domain, hostname));
if (!domainMatches) {
continue;
}
}
// Check path match if specified
if (route.match.path) {
const pathResult = PathMatcher.match(route.match.path, path);
if (pathResult.matches) {
return {
route,
pathMatch: pathResult.pathMatch || path,
pathParams: pathResult.params,
pathRemainder: pathResult.pathRemainder
};
}
}
else {
// No path specified, so domain match is sufficient
return { route };
}
}
return undefined;
}
/**
* Gets all currently active route configurations
* @returns Array of all active routes
*/
getRoutes() {
return [...this.routes];
}
/**
* Gets all hostnames that this router is configured to handle
* @returns Array of unique hostnames
*/
getHostnames() {
const hostnames = new Set();
for (const route of this.routes) {
if (!route.match.domains)
continue;
const domains = Array.isArray(route.match.domains)
? route.match.domains
: [route.match.domains];
for (const domain of domains) {
if (domain !== '*') {
hostnames.add(domain.toLowerCase());
}
}
}
return Array.from(hostnames);
}
/**
* Adds a single new route configuration
* @param route The route configuration to add
*/
addRoute(route) {
this.routes.push(route);
// Re-sort routes by priority
this.routes.sort((a, b) => {
const priorityA = a.priority ?? 0;
const priorityB = b.priority ?? 0;
return priorityB - priorityA;
});
}
/**
* Removes routes by domain pattern
* @param domain The domain pattern to remove routes for
* @returns Boolean indicating whether any routes were removed
*/
removeRoutesByDomain(domain) {
const initialCount = this.routes.length;
// Filter out routes that match the domain
this.routes = this.routes.filter(route => {
if (!route.match.domains)
return true;
const domains = Array.isArray(route.match.domains)
? route.match.domains
: [route.match.domains];
return !domains.includes(domain);
});
return this.routes.length !== initialCount;
}
/**
* Remove a specific route by reference
* @param route The route to remove
* @returns Boolean indicating if the route was found and removed
*/
removeRoute(route) {
const index = this.routes.indexOf(route);
if (index !== -1) {
this.routes.splice(index, 1);
return true;
}
return false;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaHR0cC1yb3V0ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9yb3V0aW5nL3JvdXRlci9odHRwLXJvdXRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBRTVDLE9BQU8sRUFBRSxhQUFhLEVBQUUsV0FBVyxFQUFFLE1BQU0sc0NBQXNDLENBQUM7QUF1QmxGOzs7Ozs7Ozs7Ozs7OztHQWNHO0FBQ0gsTUFBTSxPQUFPLFVBQVU7SUFRckIsWUFDRSxNQUF1QixFQUN2QixNQUFnQjtRQVRsQixrQ0FBa0M7UUFDMUIsV0FBTSxHQUFtQixFQUFFLENBQUM7UUFVbEMsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLElBQUk7WUFDdEIsS0FBSyxFQUFFLE9BQU8sQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQztZQUNsQyxJQUFJLEVBQUUsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDO1lBQ2hDLElBQUksRUFBRSxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUM7WUFDaEMsS0FBSyxFQUFFLE9BQU8sQ0FBQyxLQUFLLEVBQUUsSUFBSSxDQUFDLE9BQU8sQ0FBQztTQUNwQyxDQUFDO1FBRUYsSUFBSSxNQUFNLEVBQUUsQ0FBQztZQUNYLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDekIsQ0FBQztJQUNILENBQUM7SUFFRDs7O09BR0c7SUFDSSxTQUFTLENBQUMsTUFBc0I7UUFDckMsSUFBSSxDQUFDLE1BQU0sR0FBRyxDQUFDLEdBQUcsTUFBTSxDQUFDLENBQUM7UUFFMUIsa0RBQWtEO1FBQ2xELElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFO1lBQ3hCLE1BQU0sU0FBUyxHQUFHLENBQUMsQ0FBQyxRQUFRLElBQUksQ0FB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