621 lines
16 KiB
TypeScript
621 lines
16 KiB
TypeScript
/**
|
|
* Route Helper Functions
|
|
*
|
|
* This file provides utility functions for creating route configurations for common scenarios.
|
|
* These functions aim to simplify the creation of route configurations for typical use cases.
|
|
*
|
|
* This module includes helper functions for creating:
|
|
* - HTTP routes (createHttpRoute)
|
|
* - HTTPS routes with TLS termination (createHttpsTerminateRoute)
|
|
* - HTTP to HTTPS redirects (createHttpToHttpsRedirect)
|
|
* - HTTPS passthrough routes (createHttpsPassthroughRoute)
|
|
* - Complete HTTPS servers with redirects (createCompleteHttpsServer)
|
|
* - Load balancer routes (createLoadBalancerRoute)
|
|
* - Static file server routes (createStaticFileRoute)
|
|
* - API routes (createApiRoute)
|
|
* - WebSocket routes (createWebSocketRoute)
|
|
* - Port mapping routes (createPortMappingRoute, createOffsetPortMappingRoute)
|
|
* - Dynamic routing (createDynamicRoute, createSmartLoadBalancer)
|
|
*/
|
|
|
|
import type { IRouteConfig, IRouteMatch, IRouteAction, IRouteTarget, TPortRange, IRouteContext } from '../models/route-types.js';
|
|
|
|
/**
|
|
* Create an HTTP-only route configuration
|
|
* @param domains Domain(s) to match
|
|
* @param target Target host and port
|
|
* @param options Additional route options
|
|
* @returns Route configuration object
|
|
*/
|
|
export function createHttpRoute(
|
|
domains: string | string[],
|
|
target: { host: string | string[]; port: number },
|
|
options: Partial<IRouteConfig> = {}
|
|
): IRouteConfig {
|
|
// Create route match
|
|
const match: IRouteMatch = {
|
|
ports: options.match?.ports || 80,
|
|
domains
|
|
};
|
|
|
|
// Create route action
|
|
const action: IRouteAction = {
|
|
type: 'forward',
|
|
target
|
|
};
|
|
|
|
// Create the route config
|
|
return {
|
|
match,
|
|
action,
|
|
name: options.name || `HTTP Route for ${Array.isArray(domains) ? domains.join(', ') : domains}`,
|
|
...options
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create an HTTPS route with TLS termination (including HTTP redirect to HTTPS)
|
|
* @param domains Domain(s) to match
|
|
* @param target Target host and port
|
|
* @param options Additional route options
|
|
* @returns Route configuration object
|
|
*/
|
|
export function createHttpsTerminateRoute(
|
|
domains: string | string[],
|
|
target: { host: string | string[]; port: number },
|
|
options: {
|
|
certificate?: 'auto' | { key: string; cert: string };
|
|
httpPort?: number | number[];
|
|
httpsPort?: number | number[];
|
|
reencrypt?: boolean;
|
|
name?: string;
|
|
[key: string]: any;
|
|
} = {}
|
|
): IRouteConfig {
|
|
// Create route match
|
|
const match: IRouteMatch = {
|
|
ports: options.httpsPort || 443,
|
|
domains
|
|
};
|
|
|
|
// Create route action
|
|
const action: IRouteAction = {
|
|
type: 'forward',
|
|
target,
|
|
tls: {
|
|
mode: options.reencrypt ? 'terminate-and-reencrypt' : 'terminate',
|
|
certificate: options.certificate || 'auto'
|
|
}
|
|
};
|
|
|
|
// Create the route config
|
|
return {
|
|
match,
|
|
action,
|
|
name: options.name || `HTTPS Route for ${Array.isArray(domains) ? domains.join(', ') : domains}`,
|
|
...options
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create an HTTP to HTTPS redirect route
|
|
* @param domains Domain(s) to match
|
|
* @param httpsPort HTTPS port to redirect to (default: 443)
|
|
* @param options Additional route options
|
|
* @returns Route configuration object
|
|
*/
|
|
export function createHttpToHttpsRedirect(
|
|
domains: string | string[],
|
|
httpsPort: number = 443,
|
|
options: Partial<IRouteConfig> = {}
|
|
): IRouteConfig {
|
|
// Create route match
|
|
const match: IRouteMatch = {
|
|
ports: options.match?.ports || 80,
|
|
domains
|
|
};
|
|
|
|
// Create route action
|
|
const action: IRouteAction = {
|
|
type: 'redirect',
|
|
redirect: {
|
|
to: `https://{domain}:${httpsPort}{path}`,
|
|
status: 301
|
|
}
|
|
};
|
|
|
|
// Create the route config
|
|
return {
|
|
match,
|
|
action,
|
|
name: options.name || `HTTP to HTTPS Redirect for ${Array.isArray(domains) ? domains.join(', ') : domains}`,
|
|
...options
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create an HTTPS passthrough route (SNI-based forwarding without TLS termination)
|
|
* @param domains Domain(s) to match
|
|
* @param target Target host and port
|
|
* @param options Additional route options
|
|
* @returns Route configuration object
|
|
*/
|
|
export function createHttpsPassthroughRoute(
|
|
domains: string | string[],
|
|
target: { host: string | string[]; port: number },
|
|
options: Partial<IRouteConfig> = {}
|
|
): IRouteConfig {
|
|
// Create route match
|
|
const match: IRouteMatch = {
|
|
ports: options.match?.ports || 443,
|
|
domains
|
|
};
|
|
|
|
// Create route action
|
|
const action: IRouteAction = {
|
|
type: 'forward',
|
|
target,
|
|
tls: {
|
|
mode: 'passthrough'
|
|
}
|
|
};
|
|
|
|
// Create the route config
|
|
return {
|
|
match,
|
|
action,
|
|
name: options.name || `HTTPS Passthrough for ${Array.isArray(domains) ? domains.join(', ') : domains}`,
|
|
...options
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create a complete HTTPS server with HTTP to HTTPS redirects
|
|
* @param domains Domain(s) to match
|
|
* @param target Target host and port
|
|
* @param options Additional configuration options
|
|
* @returns Array of two route configurations (HTTPS and HTTP redirect)
|
|
*/
|
|
export function createCompleteHttpsServer(
|
|
domains: string | string[],
|
|
target: { host: string | string[]; port: number },
|
|
options: {
|
|
certificate?: 'auto' | { key: string; cert: string };
|
|
httpPort?: number | number[];
|
|
httpsPort?: number | number[];
|
|
reencrypt?: boolean;
|
|
name?: string;
|
|
[key: string]: any;
|
|
} = {}
|
|
): IRouteConfig[] {
|
|
// Create the HTTPS route
|
|
const httpsRoute = createHttpsTerminateRoute(domains, target, options);
|
|
|
|
// Create the HTTP redirect route
|
|
const httpRedirectRoute = createHttpToHttpsRedirect(
|
|
domains,
|
|
// Extract the HTTPS port from the HTTPS route - ensure it's a number
|
|
typeof options.httpsPort === 'number' ? options.httpsPort :
|
|
Array.isArray(options.httpsPort) ? options.httpsPort[0] : 443,
|
|
{
|
|
// Set the HTTP port
|
|
match: {
|
|
ports: options.httpPort || 80,
|
|
domains
|
|
},
|
|
name: `HTTP to HTTPS Redirect for ${Array.isArray(domains) ? domains.join(', ') : domains}`
|
|
}
|
|
);
|
|
|
|
return [httpsRoute, httpRedirectRoute];
|
|
}
|
|
|
|
/**
|
|
* Create a load balancer route (round-robin between multiple backend hosts)
|
|
* @param domains Domain(s) to match
|
|
* @param hosts Array of backend hosts to load balance between
|
|
* @param port Backend port
|
|
* @param options Additional route options
|
|
* @returns Route configuration object
|
|
*/
|
|
export function createLoadBalancerRoute(
|
|
domains: string | string[],
|
|
hosts: string[],
|
|
port: number,
|
|
options: {
|
|
tls?: {
|
|
mode: 'passthrough' | 'terminate' | 'terminate-and-reencrypt';
|
|
certificate?: 'auto' | { key: string; cert: string };
|
|
};
|
|
[key: string]: any;
|
|
} = {}
|
|
): IRouteConfig {
|
|
// Create route match
|
|
const match: IRouteMatch = {
|
|
ports: options.match?.ports || (options.tls ? 443 : 80),
|
|
domains
|
|
};
|
|
|
|
// Create route target
|
|
const target: IRouteTarget = {
|
|
host: hosts,
|
|
port
|
|
};
|
|
|
|
// Create route action
|
|
const action: IRouteAction = {
|
|
type: 'forward',
|
|
target
|
|
};
|
|
|
|
// Add TLS configuration if provided
|
|
if (options.tls) {
|
|
action.tls = {
|
|
mode: options.tls.mode,
|
|
certificate: options.tls.certificate || 'auto'
|
|
};
|
|
}
|
|
|
|
// Create the route config
|
|
return {
|
|
match,
|
|
action,
|
|
name: options.name || `Load Balancer for ${Array.isArray(domains) ? domains.join(', ') : domains}`,
|
|
...options
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create a static file server route
|
|
* @param domains Domain(s) to match
|
|
* @param rootDir Root directory path for static files
|
|
* @param options Additional route options
|
|
* @returns Route configuration object
|
|
*/
|
|
export function createStaticFileRoute(
|
|
domains: string | string[],
|
|
rootDir: string,
|
|
options: {
|
|
indexFiles?: string[];
|
|
serveOnHttps?: boolean;
|
|
certificate?: 'auto' | { key: string; cert: string };
|
|
httpPort?: number | number[];
|
|
httpsPort?: number | number[];
|
|
name?: string;
|
|
[key: string]: any;
|
|
} = {}
|
|
): IRouteConfig {
|
|
// Create route match
|
|
const match: IRouteMatch = {
|
|
ports: options.serveOnHttps
|
|
? (options.httpsPort || 443)
|
|
: (options.httpPort || 80),
|
|
domains
|
|
};
|
|
|
|
// Create route action
|
|
const action: IRouteAction = {
|
|
type: 'static',
|
|
static: {
|
|
root: rootDir,
|
|
index: options.indexFiles || ['index.html', 'index.htm']
|
|
}
|
|
};
|
|
|
|
// Add TLS configuration if serving on HTTPS
|
|
if (options.serveOnHttps) {
|
|
action.tls = {
|
|
mode: 'terminate',
|
|
certificate: options.certificate || 'auto'
|
|
};
|
|
}
|
|
|
|
// Create the route config
|
|
return {
|
|
match,
|
|
action,
|
|
name: options.name || `Static Files for ${Array.isArray(domains) ? domains.join(', ') : domains}`,
|
|
...options
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create an API route configuration
|
|
* @param domains Domain(s) to match
|
|
* @param apiPath API base path (e.g., "/api")
|
|
* @param target Target host and port
|
|
* @param options Additional route options
|
|
* @returns Route configuration object
|
|
*/
|
|
export function createApiRoute(
|
|
domains: string | string[],
|
|
apiPath: string,
|
|
target: { host: string | string[]; port: number },
|
|
options: {
|
|
useTls?: boolean;
|
|
certificate?: 'auto' | { key: string; cert: string };
|
|
addCorsHeaders?: boolean;
|
|
httpPort?: number | number[];
|
|
httpsPort?: number | number[];
|
|
name?: string;
|
|
[key: string]: any;
|
|
} = {}
|
|
): IRouteConfig {
|
|
// Normalize API path
|
|
const normalizedPath = apiPath.startsWith('/') ? apiPath : `/${apiPath}`;
|
|
const pathWithWildcard = normalizedPath.endsWith('/')
|
|
? `${normalizedPath}*`
|
|
: `${normalizedPath}/*`;
|
|
|
|
// Create route match
|
|
const match: IRouteMatch = {
|
|
ports: options.useTls
|
|
? (options.httpsPort || 443)
|
|
: (options.httpPort || 80),
|
|
domains,
|
|
path: pathWithWildcard
|
|
};
|
|
|
|
// Create route action
|
|
const action: IRouteAction = {
|
|
type: 'forward',
|
|
target
|
|
};
|
|
|
|
// Add TLS configuration if using HTTPS
|
|
if (options.useTls) {
|
|
action.tls = {
|
|
mode: 'terminate',
|
|
certificate: options.certificate || 'auto'
|
|
};
|
|
}
|
|
|
|
// Add CORS headers if requested
|
|
const headers: Record<string, Record<string, string>> = {};
|
|
if (options.addCorsHeaders) {
|
|
headers.response = {
|
|
'Access-Control-Allow-Origin': '*',
|
|
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
|
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
|
'Access-Control-Max-Age': '86400'
|
|
};
|
|
}
|
|
|
|
// Create the route config
|
|
return {
|
|
match,
|
|
action,
|
|
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
name: options.name || `API Route ${normalizedPath} for ${Array.isArray(domains) ? domains.join(', ') : domains}`,
|
|
priority: options.priority || 100, // Higher priority for specific path matches
|
|
...options
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create a WebSocket route configuration
|
|
* @param domains Domain(s) to match
|
|
* @param wsPath WebSocket path (e.g., "/ws")
|
|
* @param target Target WebSocket server host and port
|
|
* @param options Additional route options
|
|
* @returns Route configuration object
|
|
*/
|
|
export function createWebSocketRoute(
|
|
domains: string | string[],
|
|
wsPath: string,
|
|
target: { host: string | string[]; port: number },
|
|
options: {
|
|
useTls?: boolean;
|
|
certificate?: 'auto' | { key: string; cert: string };
|
|
httpPort?: number | number[];
|
|
httpsPort?: number | number[];
|
|
pingInterval?: number;
|
|
pingTimeout?: number;
|
|
name?: string;
|
|
[key: string]: any;
|
|
} = {}
|
|
): IRouteConfig {
|
|
// Normalize WebSocket path
|
|
const normalizedPath = wsPath.startsWith('/') ? wsPath : `/${wsPath}`;
|
|
|
|
// Create route match
|
|
const match: IRouteMatch = {
|
|
ports: options.useTls
|
|
? (options.httpsPort || 443)
|
|
: (options.httpPort || 80),
|
|
domains,
|
|
path: normalizedPath
|
|
};
|
|
|
|
// Create route action
|
|
const action: IRouteAction = {
|
|
type: 'forward',
|
|
target,
|
|
websocket: {
|
|
enabled: true,
|
|
pingInterval: options.pingInterval || 30000, // 30 seconds
|
|
pingTimeout: options.pingTimeout || 5000 // 5 seconds
|
|
}
|
|
};
|
|
|
|
// Add TLS configuration if using HTTPS
|
|
if (options.useTls) {
|
|
action.tls = {
|
|
mode: 'terminate',
|
|
certificate: options.certificate || 'auto'
|
|
};
|
|
}
|
|
|
|
// Create the route config
|
|
return {
|
|
match,
|
|
action,
|
|
name: options.name || `WebSocket Route ${normalizedPath} for ${Array.isArray(domains) ? domains.join(', ') : domains}`,
|
|
priority: options.priority || 100, // Higher priority for WebSocket routes
|
|
...options
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create a helper function that applies a port offset
|
|
* @param offset The offset to apply to the matched port
|
|
* @returns A function that adds the offset to the matched port
|
|
*/
|
|
export function createPortOffset(offset: number): (context: IRouteContext) => number {
|
|
return (context: IRouteContext) => context.port + offset;
|
|
}
|
|
|
|
/**
|
|
* Create a port mapping route with context-based port function
|
|
* @param options Port mapping route options
|
|
* @returns Route configuration object
|
|
*/
|
|
export function createPortMappingRoute(options: {
|
|
sourcePortRange: TPortRange;
|
|
targetHost: string | string[] | ((context: IRouteContext) => string | string[]);
|
|
portMapper: (context: IRouteContext) => number;
|
|
name?: string;
|
|
domains?: string | string[];
|
|
priority?: number;
|
|
[key: string]: any;
|
|
}): IRouteConfig {
|
|
// Create route match
|
|
const match: IRouteMatch = {
|
|
ports: options.sourcePortRange,
|
|
domains: options.domains
|
|
};
|
|
|
|
// Create route action
|
|
const action: IRouteAction = {
|
|
type: 'forward',
|
|
target: {
|
|
host: options.targetHost,
|
|
port: options.portMapper
|
|
}
|
|
};
|
|
|
|
// Create the route config
|
|
return {
|
|
match,
|
|
action,
|
|
name: options.name || `Port Mapping Route for ${options.domains || 'all domains'}`,
|
|
priority: options.priority,
|
|
...options
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create a simple offset port mapping route
|
|
* @param options Offset port mapping route options
|
|
* @returns Route configuration object
|
|
*/
|
|
export function createOffsetPortMappingRoute(options: {
|
|
ports: TPortRange;
|
|
targetHost: string | string[];
|
|
offset: number;
|
|
name?: string;
|
|
domains?: string | string[];
|
|
priority?: number;
|
|
[key: string]: any;
|
|
}): IRouteConfig {
|
|
return createPortMappingRoute({
|
|
sourcePortRange: options.ports,
|
|
targetHost: options.targetHost,
|
|
portMapper: (context) => context.port + options.offset,
|
|
name: options.name || `Offset Mapping (${options.offset > 0 ? '+' : ''}${options.offset}) for ${options.domains || 'all domains'}`,
|
|
domains: options.domains,
|
|
priority: options.priority,
|
|
...options
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create a dynamic route with context-based host and port mapping
|
|
* @param options Dynamic route options
|
|
* @returns Route configuration object
|
|
*/
|
|
export function createDynamicRoute(options: {
|
|
ports: TPortRange;
|
|
targetHost: (context: IRouteContext) => string | string[];
|
|
portMapper: (context: IRouteContext) => number;
|
|
name?: string;
|
|
domains?: string | string[];
|
|
path?: string;
|
|
clientIp?: string[];
|
|
priority?: number;
|
|
[key: string]: any;
|
|
}): IRouteConfig {
|
|
// Create route match
|
|
const match: IRouteMatch = {
|
|
ports: options.ports,
|
|
domains: options.domains,
|
|
path: options.path,
|
|
clientIp: options.clientIp
|
|
};
|
|
|
|
// Create route action
|
|
const action: IRouteAction = {
|
|
type: 'forward',
|
|
target: {
|
|
host: options.targetHost,
|
|
port: options.portMapper
|
|
}
|
|
};
|
|
|
|
// Create the route config
|
|
return {
|
|
match,
|
|
action,
|
|
name: options.name || `Dynamic Route for ${options.domains || 'all domains'}`,
|
|
priority: options.priority,
|
|
...options
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create a smart load balancer with dynamic domain-based backend selection
|
|
* @param options Smart load balancer options
|
|
* @returns Route configuration object
|
|
*/
|
|
export function createSmartLoadBalancer(options: {
|
|
ports: TPortRange;
|
|
domainTargets: Record<string, string | string[]>;
|
|
portMapper: (context: IRouteContext) => number;
|
|
name?: string;
|
|
defaultTarget?: string | string[];
|
|
priority?: number;
|
|
[key: string]: any;
|
|
}): IRouteConfig {
|
|
// Extract all domain keys to create the match criteria
|
|
const domains = Object.keys(options.domainTargets);
|
|
|
|
// Create the smart host selector function
|
|
const hostSelector = (context: IRouteContext) => {
|
|
const domain = context.domain || '';
|
|
return options.domainTargets[domain] || options.defaultTarget || 'localhost';
|
|
};
|
|
|
|
// Create route match
|
|
const match: IRouteMatch = {
|
|
ports: options.ports,
|
|
domains
|
|
};
|
|
|
|
// Create route action
|
|
const action: IRouteAction = {
|
|
type: 'forward',
|
|
target: {
|
|
host: hostSelector,
|
|
port: options.portMapper
|
|
}
|
|
};
|
|
|
|
// Create the route config
|
|
return {
|
|
match,
|
|
action,
|
|
name: options.name || `Smart Load Balancer for ${domains.join(', ')}`,
|
|
priority: options.priority,
|
|
...options
|
|
};
|
|
} |