mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Create an OperationRouterHandler
This allows us to route after an Operation has been parsed
This commit is contained in:
75
src/server/util/BaseRouterHandler.ts
Normal file
75
src/server/util/BaseRouterHandler.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
|
||||
import { MethodNotAllowedHttpError } from '../../util/errors/MethodNotAllowedHttpError';
|
||||
import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError';
|
||||
import type { AsyncHandlerInput, AsyncHandlerOutput } from '../../util/handlers/AsyncHandler';
|
||||
import { AsyncHandler } from '../../util/handlers/AsyncHandler';
|
||||
import { trimTrailingSlashes } from '../../util/PathUtil';
|
||||
|
||||
export interface BaseRouterHandlerArgs<T extends AsyncHandler<any, any>> {
|
||||
/**
|
||||
* The base URL of the server.
|
||||
* Not required if no value is provided for `allowedPathNames`.
|
||||
*/
|
||||
baseUrl?: string;
|
||||
/**
|
||||
* The handler to call if all checks pass.
|
||||
*/
|
||||
handler: T;
|
||||
/**
|
||||
* The allowed method(s). `*` can be used to indicate all methods are allowed.
|
||||
* Default is `[ '*' ]`.
|
||||
*/
|
||||
allowedMethods?: string[];
|
||||
/**
|
||||
* Regular expression(s) used to match the target URL.
|
||||
* The base URl without trailing slash will be stripped of before applying the regular expressions,
|
||||
* so the input will always start with a `/`.
|
||||
* Default is `[ '.*' ]`.
|
||||
*/
|
||||
allowedPathNames?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given method and path are satisfied and allows its handler to be executed if so.
|
||||
*
|
||||
* Implementations of this class should call `canHandleInput` in their `canHandle` call with the correct parameters.
|
||||
*
|
||||
* `canHandleInput` expects a ResourceIdentifier to indicate it expects the target to have been validated already.
|
||||
*/
|
||||
export abstract class BaseRouterHandler<T extends AsyncHandler<any, any>>
|
||||
extends AsyncHandler<AsyncHandlerInput<T>, AsyncHandlerOutput<T>> {
|
||||
protected readonly baseUrlLength: number;
|
||||
protected readonly handler: T;
|
||||
protected readonly allowedMethods: string[];
|
||||
protected readonly allMethods: boolean;
|
||||
protected readonly allowedPathNamesRegEx: RegExp[];
|
||||
|
||||
protected constructor(args: BaseRouterHandlerArgs<T>) {
|
||||
super();
|
||||
if (typeof args.allowedPathNames !== 'undefined' && typeof args.baseUrl !== 'string') {
|
||||
throw new Error('A value for allowedPathNames requires baseUrl to be defined.');
|
||||
}
|
||||
// Trimming trailing slash so regexes can start with `/`
|
||||
this.baseUrlLength = trimTrailingSlashes(args.baseUrl ?? '').length;
|
||||
this.handler = args.handler;
|
||||
this.allowedMethods = args.allowedMethods ?? [ '*' ];
|
||||
this.allMethods = this.allowedMethods.includes('*');
|
||||
this.allowedPathNamesRegEx = (args.allowedPathNames ?? [ '.*' ]).map((pn): RegExp => new RegExp(pn, 'u'));
|
||||
}
|
||||
|
||||
protected async canHandleInput(input: AsyncHandlerInput<T>, method: string, target: ResourceIdentifier):
|
||||
Promise<void> {
|
||||
if (!this.allMethods && !this.allowedMethods.includes(method)) {
|
||||
throw new MethodNotAllowedHttpError([ method ], `${method} is not allowed.`);
|
||||
}
|
||||
const pathName = target.path.slice(this.baseUrlLength);
|
||||
if (!this.allowedPathNamesRegEx.some((regex): boolean => regex.test(pathName))) {
|
||||
throw new NotFoundHttpError(`Cannot handle route ${pathName}`);
|
||||
}
|
||||
await this.handler.canHandle(input);
|
||||
}
|
||||
|
||||
public async handle(input: AsyncHandlerInput<T>): Promise<AsyncHandlerOutput<T>> {
|
||||
return this.handler.handle(input);
|
||||
}
|
||||
}
|
||||
16
src/server/util/OperationRouterHandler.ts
Normal file
16
src/server/util/OperationRouterHandler.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { OperationHttpHandlerInput, OperationHttpHandler } from '../OperationHttpHandler';
|
||||
import type { BaseRouterHandlerArgs } from './BaseRouterHandler';
|
||||
import { BaseRouterHandler } from './BaseRouterHandler';
|
||||
|
||||
/**
|
||||
* A {@link BaseRouterHandler} for an {@link OperationHttpHandler}.
|
||||
*/
|
||||
export class OperationRouterHandler extends BaseRouterHandler<OperationHttpHandler> {
|
||||
public constructor(args: BaseRouterHandlerArgs<OperationHttpHandler>) {
|
||||
super(args);
|
||||
}
|
||||
|
||||
public async canHandle(input: OperationHttpHandlerInput): Promise<void> {
|
||||
await super.canHandleInput(input, input.operation.method, input.operation.target);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,23 @@
|
||||
import type { TargetExtractor } from '../../http/input/identifier/TargetExtractor';
|
||||
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
|
||||
import { MethodNotAllowedHttpError } from '../../util/errors/MethodNotAllowedHttpError';
|
||||
import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError';
|
||||
import { ensureTrailingSlash, getRelativeUrl } from '../../util/PathUtil';
|
||||
import type { HttpHandlerInput } from '../HttpHandler';
|
||||
import { HttpHandler } from '../HttpHandler';
|
||||
import type { HttpHandlerInput, HttpHandler } from '../HttpHandler';
|
||||
import { BaseRouterHandler } from './BaseRouterHandler';
|
||||
import type { BaseRouterHandlerArgs } from './BaseRouterHandler';
|
||||
|
||||
export interface RouterHandlerArgs {
|
||||
baseUrl: string;
|
||||
export interface RouterHandlerArgs extends BaseRouterHandlerArgs<HttpHandler> {
|
||||
targetExtractor: TargetExtractor;
|
||||
handler: HttpHandler;
|
||||
allowedMethods: string[];
|
||||
allowedPathNames: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* An HttpHandler that checks if a given method and path are satisfied
|
||||
* and allows its handler to be executed if so.
|
||||
*
|
||||
* If `allowedMethods` contains '*' it will match all methods.
|
||||
* A {@link BaseRouterHandler} for an {@link HttpHandler}.
|
||||
* Uses a {@link TargetExtractor} to generate the target identifier.
|
||||
*/
|
||||
export class RouterHandler extends HttpHandler {
|
||||
private readonly baseUrl: string;
|
||||
export class RouterHandler extends BaseRouterHandler<HttpHandler> {
|
||||
private readonly targetExtractor: TargetExtractor;
|
||||
private readonly handler: HttpHandler;
|
||||
private readonly allowedMethods: string[];
|
||||
private readonly allMethods: boolean;
|
||||
private readonly allowedPathNamesRegEx: RegExp[];
|
||||
|
||||
public constructor(args: RouterHandlerArgs) {
|
||||
super();
|
||||
this.baseUrl = ensureTrailingSlash(args.baseUrl);
|
||||
super(args);
|
||||
this.targetExtractor = args.targetExtractor;
|
||||
this.handler = args.handler;
|
||||
this.allowedMethods = args.allowedMethods;
|
||||
this.allMethods = args.allowedMethods.includes('*');
|
||||
this.allowedPathNamesRegEx = args.allowedPathNames.map((pn): RegExp => new RegExp(pn, 'u'));
|
||||
}
|
||||
|
||||
public async canHandle(input: HttpHandlerInput): Promise<void> {
|
||||
@@ -43,20 +25,7 @@ export class RouterHandler extends HttpHandler {
|
||||
if (!request.url) {
|
||||
throw new BadRequestHttpError('Cannot handle request without a url');
|
||||
}
|
||||
if (!request.method) {
|
||||
throw new BadRequestHttpError('Cannot handle request without a method');
|
||||
}
|
||||
if (!this.allMethods && !this.allowedMethods.includes(request.method)) {
|
||||
throw new MethodNotAllowedHttpError([ request.method ], `${request.method} is not allowed.`);
|
||||
}
|
||||
const pathName = await getRelativeUrl(this.baseUrl, request, this.targetExtractor);
|
||||
if (!this.allowedPathNamesRegEx.some((regex): boolean => regex.test(pathName))) {
|
||||
throw new NotFoundHttpError(`Cannot handle route ${pathName}`);
|
||||
}
|
||||
await this.handler.canHandle(input);
|
||||
}
|
||||
|
||||
public async handle(input: HttpHandlerInput): Promise<void> {
|
||||
await this.handler.handle(input);
|
||||
const target = await this.targetExtractor.handleSafe({ request });
|
||||
await super.canHandleInput(input, request.method ?? 'UNKNOWN', target);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user