Skip to content

Commit 59e6a3d

Browse files
Merge pull request nestjs#802 from nestjs/790-feature-middleware
feature(@nestjs/core) superior MiddlewareConsumer, add exclude
2 parents 292fe21 + 4cfda07 commit 59e6a3d

19 files changed

Lines changed: 317 additions & 162 deletions

packages/common/interfaces/http/http-server.interface.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import { IncomingMessage, ServerResponse } from 'http';
2+
import { RequestMethod } from '../../enums';
23

34
export type ErrorHandler = (
4-
error: any,
5-
req: Partial<IncomingMessage>,
6-
res: ServerResponse | any,
7-
next?: Function,
8-
) => any;
9-
export type RequestHandler = (req: Partial<IncomingMessage>, res: ServerResponse | any, next?: Function) => any;
5+
error: any,
6+
req: Partial<IncomingMessage>,
7+
res: ServerResponse | any,
8+
next?: Function,
9+
) => any;
10+
export type RequestHandler = (
11+
req: Partial<IncomingMessage>,
12+
res: ServerResponse | any,
13+
next?: Function,
14+
) => any;
1015

1116
export interface HttpServer {
1217
use(handler: RequestHandler | ErrorHandler): any;
@@ -35,6 +40,9 @@ export interface HttpServer {
3540
useStaticAssets?(...args: any[]): this;
3641
setBaseViewsDir?(path: string): this;
3742
setViewEngine?(engineOrOptions: any): this;
43+
createMiddlewareFactory(
44+
method: RequestMethod,
45+
): (path: string, callback: Function) => any;
3846
getRequestMethod?(request): string;
3947
getRequestUrl?(request): string;
4048
getInstance(): any;
Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,31 @@
1-
import { MiddlewareConsumer } from './middleware-consumer.interface';
2-
import { RequestMappingMetadata } from '../request-mapping-metadata.interface';
31
import { Type } from '../type.interface';
2+
import { RouteInfo } from './middleware-configuration.interface';
3+
import { MiddlewareConsumer } from './middleware-consumer.interface';
44

55
export interface MiddlewareConfigProxy {
66
/**
7-
* Passes custom arguments to `resolve()` method of the middleware.
7+
* Delegates custom arguments to the `resolve()` method of the middleware.
88
*
99
* @param {} ...data
1010
* @returns {MiddlewareConfigProxy}
1111
*/
1212
with(...data: any[]): MiddlewareConfigProxy;
1313

1414
/**
15-
* Attaches passed either routes (strings) or controllers to the processed middleware(s).
16-
* When you pass Controller class Nest will attach middleware to every path defined within this controller.
15+
* Excludes routes from the currently processed middleware.
16+
* This excluded route has to use an exact same route path.
17+
*
18+
* @param {} ...routes
19+
* @returns {MiddlewareConfigProxy}
20+
*/
21+
exclude(...routes: (string | RouteInfo)[]): MiddlewareConfigProxy;
22+
23+
/**
24+
* Attaches passed either routes or controllers to the currently configured middleware.
25+
* If you pass a class, Nest would attach middleware to every path defined within this controller.
1726
*
1827
* @param {} ...routes
1928
* @returns {MiddlewareConsumer}
2029
*/
21-
forRoutes(...routes: (string | Type<any>)[]): MiddlewareConsumer;
30+
forRoutes(...routes: (string | Type<any> | RouteInfo)[]): MiddlewareConsumer;
2231
}
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
import { RequestMethod } from '../../enums';
12
import { Type } from '../type.interface';
23

3-
export interface MiddlewareConfiguration {
4-
middleware: any;
5-
forRoutes: (Type<any> | string)[];
4+
export interface RouteInfo {
5+
path: string;
6+
method: RequestMethod;
7+
}
8+
9+
export interface MiddlewareConfiguration<T = any> {
10+
middleware: T;
11+
forRoutes: (Type<any> | string | RouteInfo)[];
612
}
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1+
import { Type } from '../type.interface';
12
import { MiddlewareConfigProxy } from './middleware-config-proxy.interface';
23

34
export interface MiddlewareConsumer {
45
/**
5-
* Takes single middleware class or array of classes
6-
* that subsequently could be attached to the passed either routes or controllers.
6+
* Takes either middleware class/function or array of classes/functions
7+
* that subsequently shall be attached to the passed routes.
78
*
89
* @param {any|any[]} middleware
910
* @returns {MiddlewareConfigProxy}
1011
*/
11-
apply(middleware: any | any[]): MiddlewareConfigProxy;
12+
apply(...middleware: (Type<any> | Function)[]): MiddlewareConfigProxy;
1213
}
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
export type MiddlewareFunction = (req?, res?, next?) => any;
1+
export type MiddlewareFunction<
2+
TRequest = any,
3+
TResponse = any,
4+
TResult = any
5+
> = (req?: TRequest, res?: TResponse, next?: Function) => TResult;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { MiddlewareFunction } from './middleware.interface';
22

33
export interface NestMiddleware {
4-
resolve(...args): MiddlewareFunction | Promise<MiddlewareFunction>;
4+
resolve(...args: any[]): MiddlewareFunction | Promise<MiddlewareFunction>;
55
}

packages/core/adapters/express-adapter.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import * as express from 'express';
2-
import {
3-
HttpServer,
4-
RequestHandler,
5-
ErrorHandler,
6-
} from '@nestjs/common/interfaces';
7-
import { isNil, isObject } from '@nestjs/common/utils/shared.utils';
1+
import { RequestMethod } from '@nestjs/common';
2+
import { HttpServer, RequestHandler } from '@nestjs/common/interfaces';
83
import { ServeStaticOptions } from '@nestjs/common/interfaces/external/serve-static-options.interface';
4+
import { isNil, isObject } from '@nestjs/common/utils/shared.utils';
5+
import * as express from 'express';
6+
import { RouterMethodFactory } from '../helpers/router-method-factory';
97

108
export class ExpressAdapter implements HttpServer {
9+
private readonly routerMethodFactory = new RouterMethodFactory();
10+
1111
constructor(private readonly instance) {}
1212

1313
use(...args: any[]) {
@@ -133,4 +133,12 @@ export class ExpressAdapter implements HttpServer {
133133
getRequestUrl(request): string {
134134
return request.url;
135135
}
136+
137+
createMiddlewareFactory(
138+
requestMethod: RequestMethod,
139+
): (path: string, callback: Function) => any {
140+
return this.routerMethodFactory
141+
.get(this.instance, requestMethod)
142+
.bind(this.instance);
143+
}
136144
}

packages/core/adapters/fastify-adapter.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1-
import {
2-
HttpServer,
3-
RequestHandler,
4-
ErrorHandler,
5-
} from '@nestjs/common/interfaces';
6-
import { Logger } from '@nestjs/common';
1+
import { Logger, RequestMethod } from '@nestjs/common';
2+
import { ErrorHandler, RequestHandler } from '@nestjs/common/interfaces';
73
import { loadPackage } from '@nestjs/common/utils/load-package.util';
84

95
export class FastifyAdapter {
@@ -134,4 +130,16 @@ export class FastifyAdapter {
134130
getRequestUrl(request): string {
135131
return request.raw.url;
136132
}
133+
134+
createMiddlewareFactory(
135+
requestMethod: RequestMethod,
136+
): (path: string, callback: Function) => any {
137+
return (path: string, callback: Function) =>
138+
this.instance.use(path, (req, res, next) => {
139+
if (req.method === RequestMethod[requestMethod]) {
140+
return callback(req, res, next);
141+
}
142+
next();
143+
});
144+
}
137145
}

packages/core/middleware/builder.ts

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import { RequestMethod } from '@nestjs/common';
12
import { flatten } from '@nestjs/common/decorators/core/dependencies.decorator';
23
import { MiddlewareConsumer, Type } from '@nestjs/common/interfaces';
3-
import { MiddlewareConfigProxy } from '@nestjs/common/interfaces/middleware';
4+
import { MiddlewareConfigProxy, RouteInfo } from '@nestjs/common/interfaces/middleware';
45
import { MiddlewareConfiguration } from '@nestjs/common/interfaces/middleware/middleware-configuration.interface';
56
import { BindResolveMiddlewareValues } from '@nestjs/common/utils/bind-resolve-values.util';
67
import { isNil } from '@nestjs/common/utils/shared.utils';
@@ -34,19 +35,34 @@ export class MiddlewareBuilder implements MiddlewareConsumer {
3435

3536
private static ConfigProxy = class implements MiddlewareConfigProxy {
3637
private contextParameters = null;
38+
private excludedRoutes: RouteInfo[] = [];
3739
private includedRoutes: any[];
3840

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

45+
public getExcludedRoutes(): RouteInfo[] {
46+
return this.excludedRoutes;
47+
}
48+
4349
public with(...args): MiddlewareConfigProxy {
4450
this.contextParameters = args;
4551
return this;
4652
}
4753

54+
public exclude(
55+
...routes: Array<string | RouteInfo>,
56+
): MiddlewareConfigProxy {
57+
const { routesMapper } = this.builder;
58+
this.excludedRoutes = this.mapRoutesToFlatList(
59+
routes.map(route => routesMapper.mapRouteToRouteInfo(route)),
60+
);
61+
return this;
62+
}
63+
4864
public forRoutes(
49-
...routes: Array<string | any>,
65+
...routes: Array<string | Type<any> | RouteInfo>,
5066
): MiddlewareConsumer {
5167
const {
5268
middlewareCollection,
@@ -55,21 +71,40 @@ export class MiddlewareBuilder implements MiddlewareConsumer {
5571
} = this.builder;
5672

5773
const forRoutes = this.mapRoutesToFlatList(
58-
routes.map(route => routesMapper.mapRouteToRouteProps(route)),
74+
routes.map(route => routesMapper.mapRouteToRouteInfo(route)),
5975
);
6076
const configuration = {
6177
middleware: bindValuesToResolve(
6278
this.includedRoutes,
6379
this.contextParameters,
6480
),
65-
forRoutes,
81+
forRoutes: forRoutes.filter(route => !this.isRouteExcluded(route)),
6682
};
6783
middlewareCollection.add(configuration);
6884
return this.builder;
6985
}
7086

71-
private mapRoutesToFlatList(forRoutes) {
87+
private mapRoutesToFlatList(forRoutes): RouteInfo[] {
7288
return forRoutes.reduce((a, b) => a.concat(b));
7389
}
90+
91+
private isRouteExcluded(routeInfo: RouteInfo): boolean {
92+
const pathLastIndex = routeInfo.path.length - 1;
93+
const validatedRoutePath =
94+
routeInfo.path[pathLastIndex] === '/'
95+
? routeInfo.path.slice(0, pathLastIndex)
96+
: routeInfo.path;
97+
98+
return this.excludedRoutes.some(excluded => {
99+
const isPathEqual = validatedRoutePath === excluded.path;
100+
if (!isPathEqual) {
101+
return false;
102+
}
103+
return (
104+
routeInfo.method === excluded.method ||
105+
excluded.method === RequestMethod.ALL
106+
);
107+
});
108+
}
74109
};
75110
}

packages/core/middleware/middleware-module.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import { HttpServer } from '@nestjs/common';
12
import { RequestMethod } from '@nestjs/common/enums/request-method.enum';
2-
import { MiddlewareConfiguration } from '@nestjs/common/interfaces/middleware/middleware-configuration.interface';
3+
import { MiddlewareConfiguration, RouteInfo } from '@nestjs/common/interfaces/middleware/middleware-configuration.interface';
34
import { NestMiddleware } from '@nestjs/common/interfaces/middleware/nest-middleware.interface';
45
import { NestModule } from '@nestjs/common/interfaces/modules/nest-module.interface';
56
import { Type } from '@nestjs/common/interfaces/type.interface';
@@ -8,7 +9,6 @@ import { ApplicationConfig } from '../application-config';
89
import { InvalidMiddlewareException } from '../errors/exceptions/invalid-middleware.exception';
910
import { RuntimeException } from '../errors/exceptions/runtime.exception';
1011
import { ExceptionsHandler } from '../exceptions/exceptions-handler';
11-
import { RouterMethodFactory } from '../helpers/router-method-factory';
1212
import { NestContainer } from '../injector/container';
1313
import { Module } from '../injector/module';
1414
import { RouterExceptionFilters } from '../router/router-exception-filters';
@@ -20,7 +20,6 @@ import { RoutesMapper } from './routes-mapper';
2020

2121
export class MiddlewareModule {
2222
private readonly routerProxy = new RouterProxy();
23-
private readonly routerMethodFactory = new RouterMethodFactory();
2423
private routerExceptionFilter: RouterExceptionFilters;
2524
private routesMapper: RoutesMapper;
2625
private resolver: MiddlewareResolver;
@@ -108,10 +107,10 @@ export class MiddlewareModule {
108107
) {
109108
const { forRoutes } = config;
110109
await Promise.all(
111-
forRoutes.map(async (routePath: string) => {
110+
forRoutes.map(async (routeInfo: RouteInfo) => {
112111
await this.registerRouteMiddleware(
113112
middlewareContainer,
114-
routePath,
113+
routeInfo,
115114
config,
116115
module,
117116
applicationRef,
@@ -122,7 +121,7 @@ export class MiddlewareModule {
122121

123122
public async registerRouteMiddleware(
124123
middlewareContainer: MiddlewareContainer,
125-
routePath: string,
124+
routeInfo: RouteInfo,
126125
config: MiddlewareConfiguration,
127126
module: string,
128127
applicationRef: any,
@@ -141,8 +140,8 @@ export class MiddlewareModule {
141140
instance,
142141
metatype,
143142
applicationRef,
144-
RequestMethod.ALL,
145-
routePath,
143+
routeInfo.method,
144+
routeInfo.path,
146145
);
147146
}),
148147
);
@@ -151,7 +150,7 @@ export class MiddlewareModule {
151150
private async bindHandler(
152151
instance: NestMiddleware,
153152
metatype: Type<NestMiddleware>,
154-
applicationRef: any,
153+
applicationRef: HttpServer,
155154
method: RequestMethod,
156155
path: string,
157156
) {
@@ -163,13 +162,16 @@ export class MiddlewareModule {
163162
instance.resolve,
164163
undefined,
165164
);
166-
const router = this.routerMethodFactory
167-
.get(applicationRef, method)
168-
.bind(applicationRef);
169-
170-
const bindWithProxy = obj =>
171-
this.bindHandlerWithProxy(exceptionsHandler, router, obj, path);
165+
const router = applicationRef.createMiddlewareFactory(method);
166+
const bindWithProxy = middlewareInstance =>
167+
this.bindHandlerWithProxy(
168+
exceptionsHandler,
169+
router,
170+
middlewareInstance,
171+
path,
172+
);
172173
const resolve = instance.resolve();
174+
173175
if (!(resolve instanceof Promise)) {
174176
bindWithProxy(resolve);
175177
return;

0 commit comments

Comments
 (0)