Skip to content

Commit

Permalink
Merge pull request nestjs#802 from nestjs/790-feature-middleware
Browse files Browse the repository at this point in the history
feature(@nestjs/core) superior MiddlewareConsumer, add exclude
  • Loading branch information
kamilmysliwiec authored Jun 21, 2018
2 parents 292fe21 + 4cfda07 commit 59e6a3d
Show file tree
Hide file tree
Showing 19 changed files with 317 additions and 162 deletions.
20 changes: 14 additions & 6 deletions packages/common/interfaces/http/http-server.interface.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { IncomingMessage, ServerResponse } from 'http';
import { RequestMethod } from '../../enums';

export type ErrorHandler = (
error: any,
req: Partial<IncomingMessage>,
res: ServerResponse | any,
next?: Function,
) => any;
export type RequestHandler = (req: Partial<IncomingMessage>, res: ServerResponse | any, next?: Function) => any;
error: any,
req: Partial<IncomingMessage>,
res: ServerResponse | any,
next?: Function,
) => any;
export type RequestHandler = (
req: Partial<IncomingMessage>,
res: ServerResponse | any,
next?: Function,
) => any;

export interface HttpServer {
use(handler: RequestHandler | ErrorHandler): any;
Expand Down Expand Up @@ -35,6 +40,9 @@ export interface HttpServer {
useStaticAssets?(...args: any[]): this;
setBaseViewsDir?(path: string): this;
setViewEngine?(engineOrOptions: any): this;
createMiddlewareFactory(
method: RequestMethod,
): (path: string, callback: Function) => any;
getRequestMethod?(request): string;
getRequestUrl?(request): string;
getInstance(): any;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
import { MiddlewareConsumer } from './middleware-consumer.interface';
import { RequestMappingMetadata } from '../request-mapping-metadata.interface';
import { Type } from '../type.interface';
import { RouteInfo } from './middleware-configuration.interface';
import { MiddlewareConsumer } from './middleware-consumer.interface';

export interface MiddlewareConfigProxy {
/**
* Passes custom arguments to `resolve()` method of the middleware.
* Delegates custom arguments to the `resolve()` method of the middleware.
*
* @param {} ...data
* @returns {MiddlewareConfigProxy}
*/
with(...data: any[]): MiddlewareConfigProxy;

/**
* Attaches passed either routes (strings) or controllers to the processed middleware(s).
* When you pass Controller class Nest will attach middleware to every path defined within this controller.
* Excludes routes from the currently processed middleware.
* This excluded route has to use an exact same route path.
*
* @param {} ...routes
* @returns {MiddlewareConfigProxy}
*/
exclude(...routes: (string | RouteInfo)[]): MiddlewareConfigProxy;

/**
* Attaches passed either routes or controllers to the currently configured middleware.
* If you pass a class, Nest would attach middleware to every path defined within this controller.
*
* @param {} ...routes
* @returns {MiddlewareConsumer}
*/
forRoutes(...routes: (string | Type<any>)[]): MiddlewareConsumer;
forRoutes(...routes: (string | Type<any> | RouteInfo)[]): MiddlewareConsumer;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { RequestMethod } from '../../enums';
import { Type } from '../type.interface';

export interface MiddlewareConfiguration {
middleware: any;
forRoutes: (Type<any> | string)[];
export interface RouteInfo {
path: string;
method: RequestMethod;
}

export interface MiddlewareConfiguration<T = any> {
middleware: T;
forRoutes: (Type<any> | string | RouteInfo)[];
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Type } from '../type.interface';
import { MiddlewareConfigProxy } from './middleware-config-proxy.interface';

export interface MiddlewareConsumer {
/**
* Takes single middleware class or array of classes
* that subsequently could be attached to the passed either routes or controllers.
* Takes either middleware class/function or array of classes/functions
* that subsequently shall be attached to the passed routes.
*
* @param {any|any[]} middleware
* @returns {MiddlewareConfigProxy}
*/
apply(middleware: any | any[]): MiddlewareConfigProxy;
apply(...middleware: (Type<any> | Function)[]): MiddlewareConfigProxy;
}
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export type MiddlewareFunction = (req?, res?, next?) => any;
export type MiddlewareFunction<
TRequest = any,
TResponse = any,
TResult = any
> = (req?: TRequest, res?: TResponse, next?: Function) => TResult;
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MiddlewareFunction } from './middleware.interface';

export interface NestMiddleware {
resolve(...args): MiddlewareFunction | Promise<MiddlewareFunction>;
resolve(...args: any[]): MiddlewareFunction | Promise<MiddlewareFunction>;
}
22 changes: 15 additions & 7 deletions packages/core/adapters/express-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import * as express from 'express';
import {
HttpServer,
RequestHandler,
ErrorHandler,
} from '@nestjs/common/interfaces';
import { isNil, isObject } from '@nestjs/common/utils/shared.utils';
import { RequestMethod } from '@nestjs/common';
import { HttpServer, RequestHandler } from '@nestjs/common/interfaces';
import { ServeStaticOptions } from '@nestjs/common/interfaces/external/serve-static-options.interface';
import { isNil, isObject } from '@nestjs/common/utils/shared.utils';
import * as express from 'express';
import { RouterMethodFactory } from '../helpers/router-method-factory';

export class ExpressAdapter implements HttpServer {
private readonly routerMethodFactory = new RouterMethodFactory();

constructor(private readonly instance) {}

use(...args: any[]) {
Expand Down Expand Up @@ -133,4 +133,12 @@ export class ExpressAdapter implements HttpServer {
getRequestUrl(request): string {
return request.url;
}

createMiddlewareFactory(
requestMethod: RequestMethod,
): (path: string, callback: Function) => any {
return this.routerMethodFactory
.get(this.instance, requestMethod)
.bind(this.instance);
}
}
20 changes: 14 additions & 6 deletions packages/core/adapters/fastify-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import {
HttpServer,
RequestHandler,
ErrorHandler,
} from '@nestjs/common/interfaces';
import { Logger } from '@nestjs/common';
import { Logger, RequestMethod } from '@nestjs/common';
import { ErrorHandler, RequestHandler } from '@nestjs/common/interfaces';
import { loadPackage } from '@nestjs/common/utils/load-package.util';

export class FastifyAdapter {
Expand Down Expand Up @@ -134,4 +130,16 @@ export class FastifyAdapter {
getRequestUrl(request): string {
return request.raw.url;
}

createMiddlewareFactory(
requestMethod: RequestMethod,
): (path: string, callback: Function) => any {
return (path: string, callback: Function) =>
this.instance.use(path, (req, res, next) => {
if (req.method === RequestMethod[requestMethod]) {
return callback(req, res, next);
}
next();
});
}
}
45 changes: 40 additions & 5 deletions packages/core/middleware/builder.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { RequestMethod } from '@nestjs/common';
import { flatten } from '@nestjs/common/decorators/core/dependencies.decorator';
import { MiddlewareConsumer, Type } from '@nestjs/common/interfaces';
import { MiddlewareConfigProxy } from '@nestjs/common/interfaces/middleware';
import { MiddlewareConfigProxy, RouteInfo } from '@nestjs/common/interfaces/middleware';
import { MiddlewareConfiguration } from '@nestjs/common/interfaces/middleware/middleware-configuration.interface';
import { BindResolveMiddlewareValues } from '@nestjs/common/utils/bind-resolve-values.util';
import { isNil } from '@nestjs/common/utils/shared.utils';
Expand Down Expand Up @@ -34,19 +35,34 @@ export class MiddlewareBuilder implements MiddlewareConsumer {

private static ConfigProxy = class implements MiddlewareConfigProxy {
private contextParameters = null;
private excludedRoutes: RouteInfo[] = [];
private includedRoutes: any[];

constructor(private readonly builder: MiddlewareBuilder, middleware) {
this.includedRoutes = filterMiddleware(middleware);
}

public getExcludedRoutes(): RouteInfo[] {
return this.excludedRoutes;
}

public with(...args): MiddlewareConfigProxy {
this.contextParameters = args;
return this;
}

public exclude(
...routes: Array<string | RouteInfo>,
): MiddlewareConfigProxy {
const { routesMapper } = this.builder;
this.excludedRoutes = this.mapRoutesToFlatList(
routes.map(route => routesMapper.mapRouteToRouteInfo(route)),
);
return this;
}

public forRoutes(
...routes: Array<string | any>,
...routes: Array<string | Type<any> | RouteInfo>,
): MiddlewareConsumer {
const {
middlewareCollection,
Expand All @@ -55,21 +71,40 @@ export class MiddlewareBuilder implements MiddlewareConsumer {
} = this.builder;

const forRoutes = this.mapRoutesToFlatList(
routes.map(route => routesMapper.mapRouteToRouteProps(route)),
routes.map(route => routesMapper.mapRouteToRouteInfo(route)),
);
const configuration = {
middleware: bindValuesToResolve(
this.includedRoutes,
this.contextParameters,
),
forRoutes,
forRoutes: forRoutes.filter(route => !this.isRouteExcluded(route)),
};
middlewareCollection.add(configuration);
return this.builder;
}

private mapRoutesToFlatList(forRoutes) {
private mapRoutesToFlatList(forRoutes): RouteInfo[] {
return forRoutes.reduce((a, b) => a.concat(b));
}

private isRouteExcluded(routeInfo: RouteInfo): boolean {
const pathLastIndex = routeInfo.path.length - 1;
const validatedRoutePath =
routeInfo.path[pathLastIndex] === '/'
? routeInfo.path.slice(0, pathLastIndex)
: routeInfo.path;

return this.excludedRoutes.some(excluded => {
const isPathEqual = validatedRoutePath === excluded.path;
if (!isPathEqual) {
return false;
}
return (
routeInfo.method === excluded.method ||
excluded.method === RequestMethod.ALL
);
});
}
};
}
32 changes: 17 additions & 15 deletions packages/core/middleware/middleware-module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { HttpServer } from '@nestjs/common';
import { RequestMethod } from '@nestjs/common/enums/request-method.enum';
import { MiddlewareConfiguration } from '@nestjs/common/interfaces/middleware/middleware-configuration.interface';
import { MiddlewareConfiguration, RouteInfo } from '@nestjs/common/interfaces/middleware/middleware-configuration.interface';
import { NestMiddleware } from '@nestjs/common/interfaces/middleware/nest-middleware.interface';
import { NestModule } from '@nestjs/common/interfaces/modules/nest-module.interface';
import { Type } from '@nestjs/common/interfaces/type.interface';
Expand All @@ -8,7 +9,6 @@ import { ApplicationConfig } from '../application-config';
import { InvalidMiddlewareException } from '../errors/exceptions/invalid-middleware.exception';
import { RuntimeException } from '../errors/exceptions/runtime.exception';
import { ExceptionsHandler } from '../exceptions/exceptions-handler';
import { RouterMethodFactory } from '../helpers/router-method-factory';
import { NestContainer } from '../injector/container';
import { Module } from '../injector/module';
import { RouterExceptionFilters } from '../router/router-exception-filters';
Expand All @@ -20,7 +20,6 @@ import { RoutesMapper } from './routes-mapper';

export class MiddlewareModule {
private readonly routerProxy = new RouterProxy();
private readonly routerMethodFactory = new RouterMethodFactory();
private routerExceptionFilter: RouterExceptionFilters;
private routesMapper: RoutesMapper;
private resolver: MiddlewareResolver;
Expand Down Expand Up @@ -108,10 +107,10 @@ export class MiddlewareModule {
) {
const { forRoutes } = config;
await Promise.all(
forRoutes.map(async (routePath: string) => {
forRoutes.map(async (routeInfo: RouteInfo) => {
await this.registerRouteMiddleware(
middlewareContainer,
routePath,
routeInfo,
config,
module,
applicationRef,
Expand All @@ -122,7 +121,7 @@ export class MiddlewareModule {

public async registerRouteMiddleware(
middlewareContainer: MiddlewareContainer,
routePath: string,
routeInfo: RouteInfo,
config: MiddlewareConfiguration,
module: string,
applicationRef: any,
Expand All @@ -141,8 +140,8 @@ export class MiddlewareModule {
instance,
metatype,
applicationRef,
RequestMethod.ALL,
routePath,
routeInfo.method,
routeInfo.path,
);
}),
);
Expand All @@ -151,7 +150,7 @@ export class MiddlewareModule {
private async bindHandler(
instance: NestMiddleware,
metatype: Type<NestMiddleware>,
applicationRef: any,
applicationRef: HttpServer,
method: RequestMethod,
path: string,
) {
Expand All @@ -163,13 +162,16 @@ export class MiddlewareModule {
instance.resolve,
undefined,
);
const router = this.routerMethodFactory
.get(applicationRef, method)
.bind(applicationRef);

const bindWithProxy = obj =>
this.bindHandlerWithProxy(exceptionsHandler, router, obj, path);
const router = applicationRef.createMiddlewareFactory(method);
const bindWithProxy = middlewareInstance =>
this.bindHandlerWithProxy(
exceptionsHandler,
router,
middlewareInstance,
path,
);
const resolve = instance.resolve();

if (!(resolve instanceof Promise)) {
bindWithProxy(resolve);
return;
Expand Down
Loading

0 comments on commit 59e6a3d

Please sign in to comment.