feat(smarts3-server): Introduce native custom S3 server implementation (Smarts3Server) with routing, middleware, context, filesystem store, controllers and XML utilities; add SmartXml and AWS SDK test; keep optional legacy s3rver backend.
This commit is contained in:
129
ts/classes/router.ts
Normal file
129
ts/classes/router.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import type { S3Context } from './context.js';
|
||||
|
||||
export type RouteHandler = (
|
||||
req: plugins.http.IncomingMessage,
|
||||
res: plugins.http.ServerResponse,
|
||||
ctx: S3Context,
|
||||
params: Record<string, string>
|
||||
) => Promise<void>;
|
||||
|
||||
export interface IRouteMatch {
|
||||
handler: RouteHandler;
|
||||
params: Record<string, string>;
|
||||
}
|
||||
|
||||
interface IRoute {
|
||||
method: string;
|
||||
pattern: RegExp;
|
||||
paramNames: string[];
|
||||
handler: RouteHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple HTTP router with pattern matching for S3 routes
|
||||
*/
|
||||
export class S3Router {
|
||||
private routes: IRoute[] = [];
|
||||
|
||||
/**
|
||||
* Add a route with pattern matching
|
||||
* Supports patterns like:
|
||||
* - "/" (exact match)
|
||||
* - "/:bucket" (single param)
|
||||
* - "/:bucket/:key*" (param with wildcard - captures everything after)
|
||||
*/
|
||||
public add(method: string, pattern: string, handler: RouteHandler): void {
|
||||
const { regex, paramNames } = this.convertPatternToRegex(pattern);
|
||||
|
||||
this.routes.push({
|
||||
method: method.toUpperCase(),
|
||||
pattern: regex,
|
||||
paramNames,
|
||||
handler,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Match a request to a route
|
||||
*/
|
||||
public match(method: string, pathname: string): IRouteMatch | null {
|
||||
// Normalize pathname: remove trailing slash unless it's root
|
||||
const normalizedPath = pathname === '/' ? pathname : pathname.replace(/\/$/, '');
|
||||
|
||||
for (const route of this.routes) {
|
||||
if (route.method !== method.toUpperCase()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const match = normalizedPath.match(route.pattern);
|
||||
if (match) {
|
||||
// Extract params from captured groups
|
||||
const params: Record<string, string> = {};
|
||||
for (let i = 0; i < route.paramNames.length; i++) {
|
||||
params[route.paramNames[i]] = decodeURIComponent(match[i + 1] || '');
|
||||
}
|
||||
|
||||
return {
|
||||
handler: route.handler,
|
||||
params,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert path pattern to RegExp
|
||||
* Examples:
|
||||
* - "/" → /^\/$/
|
||||
* - "/:bucket" → /^\/([^/]+)$/
|
||||
* - "/:bucket/:key*" → /^\/([^/]+)\/(.+)$/
|
||||
*/
|
||||
private convertPatternToRegex(pattern: string): { regex: RegExp; paramNames: string[] } {
|
||||
const paramNames: string[] = [];
|
||||
let regexStr = pattern;
|
||||
|
||||
// Process all params in a single pass to maintain order
|
||||
regexStr = regexStr.replace(/:(\w+)(\*)?/g, (match, paramName, isWildcard) => {
|
||||
paramNames.push(paramName);
|
||||
// :param* captures rest of path, :param captures single segment
|
||||
return isWildcard ? '(.+)' : '([^/]+)';
|
||||
});
|
||||
|
||||
// Escape special regex characters
|
||||
regexStr = regexStr.replace(/\//g, '\\/');
|
||||
|
||||
// Add anchors
|
||||
regexStr = `^${regexStr}$`;
|
||||
|
||||
return {
|
||||
regex: new RegExp(regexStr),
|
||||
paramNames,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience methods for common HTTP methods
|
||||
*/
|
||||
public get(pattern: string, handler: RouteHandler): void {
|
||||
this.add('GET', pattern, handler);
|
||||
}
|
||||
|
||||
public put(pattern: string, handler: RouteHandler): void {
|
||||
this.add('PUT', pattern, handler);
|
||||
}
|
||||
|
||||
public post(pattern: string, handler: RouteHandler): void {
|
||||
this.add('POST', pattern, handler);
|
||||
}
|
||||
|
||||
public delete(pattern: string, handler: RouteHandler): void {
|
||||
this.add('DELETE', pattern, handler);
|
||||
}
|
||||
|
||||
public head(pattern: string, handler: RouteHandler): void {
|
||||
this.add('HEAD', pattern, handler);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user