Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): let the middleware can get the params in the global prefix #10390

Merged
merged 21 commits into from
Feb 3, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
75d5d64
fix(core): let the middleware can get the params in the global prefix
CodyTseng Oct 10, 2022
b7f458f
test: add the middleware get the params from global prefix test
CodyTseng Oct 11, 2022
c437ac9
fix(core): add leading slash to excluded paths
CodyTseng Oct 15, 2022
3d16326
style(core): add missing period
CodyTseng Oct 15, 2022
4c4c13b
Merge branch 'master' into fix-middleware-global-prefix
CodyTseng Nov 15, 2022
cc7e829
feat: version-aware middleware
CodyTseng Nov 19, 2022
4a27d26
test: update the middleware get the params from global prefix test
CodyTseng Nov 19, 2022
d5f434c
Merge branch 'nestjs:master' into fix-middleware-global-prefix
CodyTseng Nov 19, 2022
fdc5f34
feat: move test
CodyTseng Nov 23, 2022
758cd2d
refactor: wrap get paths logic into a private function
CodyTseng Nov 23, 2022
7d1fa9a
test: global prefix options
CodyTseng Nov 23, 2022
60772c3
feat: use `mapToExcludeRoute()` when set global prefix
CodyTseng Nov 23, 2022
151a67b
refactor: `applicationConfig` instead `(instance as any).config`
CodyTseng Nov 24, 2022
daa2054
style: keep up with current standards
CodyTseng Nov 24, 2022
ecba3ba
refactor: create a separate class to retrieve the paths
CodyTseng Nov 24, 2022
a1f17e2
refactor: rename `getPaths` to `extractPathsFrom`
CodyTseng Nov 24, 2022
25dd621
Merge branch 'master' into fix-middleware-global-prefix
CodyTseng Feb 1, 2023
33fe1d0
fix: duplicate identifier
CodyTseng Feb 1, 2023
c6dbc05
refactor: MiddlewareModule constructor parameters
CodyTseng Feb 1, 2023
5e5828b
refactor: delete unused import
CodyTseng Feb 2, 2023
6d64d91
Update packages/core/middleware/route-info-path-extractor.ts
CodyTseng Feb 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 24 additions & 0 deletions integration/hello-world/e2e/middleware-with-versioning.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,26 @@ describe('Middleware', () => {
});
});

describe('when using default URI versioning with the global prefix', () => {
beforeEach(async () => {
app = await createAppWithVersioning(
{
type: VersioningType.URI,
defaultVersion: VERSION_NEUTRAL,
},
async (app: INestApplication) => {
app.setGlobalPrefix('api');
},
);
});

it(`forRoutes({ path: '/versioned', version: '1', method: RequestMethod.ALL })`, () => {
return request(app.getHttpServer())
.get('/api/v1/versioned')
.expect(200, VERSIONED_VALUE);
});
});

describe('when using HEADER versioning', () => {
beforeEach(async () => {
app = await createAppWithVersioning({
Expand Down Expand Up @@ -133,6 +153,7 @@ describe('Middleware', () => {

async function createAppWithVersioning(
versioningOptions: VersioningOptions,
beforeInit?: (app: INestApplication) => Promise<void>,
): Promise<INestApplication> {
const app = (
await Test.createTestingModule({
Expand All @@ -141,6 +162,9 @@ async function createAppWithVersioning(
).createNestApplication();

app.enableVersioning(versioningOptions);
if (beforeInit) {
await beforeInit(app);
}
await app.init();

return app;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,17 @@ describe('Global prefix', () => {
await request(server).get('/api/v1/middleware/foo').expect(404);
});

it(`should get the params in the global prefix`, async () => {
app.setGlobalPrefix('/api/:tenantId');

server = app.getHttpServer();
await app.init();

await request(server)
.get('/api/test/params')
.expect(200, { '0': 'params', tenantId: 'test' });
});

afterEach(async () => {
await app.close();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ export class AppController {
return 'Hello: ' + req.extras?.data;
}

@Get('params')
getParams(@Req() req): any {
return req.middlewareParams;
}

@Get('health')
getHealth(): string {
return 'up';
Expand Down
5 changes: 5 additions & 0 deletions integration/nest-application/global-prefix/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export class AppModule {
req.extras = { data: 'Data attached in middleware' };
next();
})
.forRoutes({ path: '*', method: RequestMethod.GET })
.apply((req, res, next) => {
req.middlewareParams = req.params;
next();
})
.forRoutes({ path: '*', method: RequestMethod.GET });
}
}
89 changes: 52 additions & 37 deletions packages/core/middleware/middleware-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import {
addLeadingSlash,
isUndefined,
stripEndSlash,
} from '@nestjs/common/utils/shared.utils';
import { ApplicationConfig } from '../application-config';
import { InvalidMiddlewareException } from '../errors/exceptions/invalid-middleware.exception';
Expand Down Expand Up @@ -266,56 +267,70 @@ export class MiddlewareModule {

private async registerHandler(
applicationRef: HttpServer,
{ path, method, version }: RouteInfo,
routeInfo: RouteInfo,
proxy: <TRequest, TResponse>(
req: TRequest,
res: TResponse,
next: () => void,
) => void,
) {
const prefix = this.config.getGlobalPrefix();
const { method } = routeInfo;
const paths = this.getPaths(routeInfo);
const isMethodAll = isRequestMethodAll(method);
const requestMethod = RequestMethod[method];
const router = await applicationRef.createMiddlewareFactory(method);
paths.forEach(path =>
router(
path,
isMethodAll
? proxy
: <TRequest, TResponse>(
req: TRequest,
res: TResponse,
next: () => void,
) => {
if (applicationRef.getRequestMethod(req) === requestMethod) {
return proxy(req, res, next);
}
return next();
},
),
);
}

private getPaths({ path, method, version }: RouteInfo) {
const prefixPath = stripEndSlash(
addLeadingSlash(this.config.getGlobalPrefix()),
);
const excludedRoutes = this.config.getGlobalPrefixOptions().exclude;
if (
(Array.isArray(excludedRoutes) &&
isRouteExcluded(excludedRoutes, path, method)) ||
['*', '/*', '(.*)', '/(.*)'].includes(path)
) {
path = addLeadingSlash(path);
} else {
const basePath = addLeadingSlash(prefix);
if (basePath?.endsWith('/') && path?.startsWith('/')) {
// strip slash when a wildcard is being used
// and global prefix has been set
path = path?.slice(1);
}
path = basePath + path;
}

const applicationVersioningConfig = this.config.getVersioning();
if (version && applicationVersioningConfig.type === VersioningType.URI) {
let versionPath = '';
if (version && applicationVersioningConfig?.type === VersioningType.URI) {
const versionPrefix = this.routePathFactory.getVersionPrefix(
applicationVersioningConfig,
);
path = `/${versionPrefix}${version.toString()}${path}`;
versionPath = addLeadingSlash(versionPrefix + version.toString());
}

const isMethodAll = isRequestMethodAll(method);
const requestMethod = RequestMethod[method];
const router = await applicationRef.createMiddlewareFactory(method);
router(
path,
isMethodAll
? proxy
: <TRequest, TResponse>(
req: TRequest,
res: TResponse,
next: () => void,
) => {
if (applicationRef.getRequestMethod(req) === requestMethod) {
return proxy(req, res, next);
}
return next();
},
);
if (['*', '/*', '/*/', '(.*)', '/(.*)'].includes(path)) {
return Array.isArray(excludedRoutes)
? [
prefixPath + versionPath + addLeadingSlash(path),
...excludedRoutes.map(
route => versionPath + addLeadingSlash(route.path),
),
]
: [prefixPath + versionPath + addLeadingSlash(path)];
}

if (
Array.isArray(excludedRoutes) &&
isRouteExcluded(excludedRoutes, path, method)
) {
return [versionPath + addLeadingSlash(path)];
}

return [prefixPath + versionPath + addLeadingSlash(path)];
}
}
26 changes: 20 additions & 6 deletions packages/core/middleware/utils.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
import { RequestMethod } from '@nestjs/common';
import { HttpServer, RouteInfo, Type } from '@nestjs/common/interfaces';
import { isFunction } from '@nestjs/common/utils/shared.utils';
import {
addLeadingSlash,
isFunction,
isString,
} from '@nestjs/common/utils/shared.utils';
import { iterate } from 'iterare';
import * as pathToRegexp from 'path-to-regexp';
import { v4 as uuid } from 'uuid';
import { ExcludeRouteMetadata } from '../router/interfaces/exclude-route-metadata.interface';
import { isRouteExcluded } from '../router/utils';

export const mapToExcludeRoute = (
routes: RouteInfo[],
routes: (string | RouteInfo)[],
): ExcludeRouteMetadata[] => {
return routes.map(({ path, method }) => ({
pathRegex: pathToRegexp(path),
requestMethod: method,
}));
return routes.map(route => {
if (isString(route)) {
return {
path: route,
requestMethod: RequestMethod.ALL,
pathRegex: pathToRegexp(addLeadingSlash(route)),
};
}
return {
path: route.path,
requestMethod: route.method,
pathRegex: pathToRegexp(addLeadingSlash(route.path)),
};
});
};

export const filterMiddleware = <T extends Function | Type<any> = any>(
Expand Down
22 changes: 4 additions & 18 deletions packages/core/nest-application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@ import {
NestHybridApplicationOptions,
NestInterceptor,
PipeTransform,
RequestMethod,
VersioningOptions,
VersioningType,
WebSocketAdapter,
} from '@nestjs/common';
import {
RouteInfo,
GlobalPrefixOptions,
NestApplicationOptions,
} from '@nestjs/common/interfaces';
Expand All @@ -31,16 +29,15 @@ import {
} from '@nestjs/common/utils/shared.utils';
import { iterate } from 'iterare';
import { platform } from 'os';
import * as pathToRegexp from 'path-to-regexp';
import { AbstractHttpAdapter } from './adapters';
import { ApplicationConfig } from './application-config';
import { MESSAGES } from './constants';
import { optionalRequire } from './helpers/optional-require';
import { NestContainer } from './injector/container';
import { MiddlewareContainer } from './middleware/container';
import { MiddlewareModule } from './middleware/middleware-module';
import { mapToExcludeRoute } from './middleware/utils';
import { NestApplicationContext } from './nest-application-context';
import { ExcludeRouteMetadata } from './router/interfaces/exclude-route-metadata.interface';
import { Resolver } from './router/interfaces/resolver.interface';
import { RoutePathFactory } from './router/route-path-factory';
import { RoutesResolver } from './router/routes-resolver';
Expand Down Expand Up @@ -336,20 +333,9 @@ export class NestApplication
public setGlobalPrefix(prefix: string, options?: GlobalPrefixOptions): this {
this.config.setGlobalPrefix(prefix);
if (options) {
const exclude = options?.exclude.map(
(route: string | RouteInfo): ExcludeRouteMetadata => {
if (isString(route)) {
return {
requestMethod: RequestMethod.ALL,
pathRegex: pathToRegexp(addLeadingSlash(route)),
};
}
return {
requestMethod: route.method,
pathRegex: pathToRegexp(addLeadingSlash(route.path)),
};
},
);
const exclude = options?.exclude
? mapToExcludeRoute(options.exclude)
: [];
this.config.setGlobalPrefixOptions({
...options,
exclude,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { RequestMethod } from '@nestjs/common';

export interface ExcludeRouteMetadata {
/**
* Route path.
*/
path: string;

/**
* Regular expression representing the route path.
*/
Expand Down
3 changes: 2 additions & 1 deletion packages/core/router/utils/exclude-route.util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { RequestMethod } from '@nestjs/common';
import { addLeadingSlash } from '@nestjs/common/utils/shared.utils';
import { ExcludeRouteMetadata } from '../interfaces/exclude-route-metadata.interface';

export const isRequestMethodAll = (method: RequestMethod) => {
Expand All @@ -15,7 +16,7 @@ export function isRouteExcluded(
isRequestMethodAll(route.requestMethod) ||
route.requestMethod === requestMethod
) {
return route.pathRegex.exec(path);
return route.pathRegex.exec(addLeadingSlash(path));
}
return false;
});
Expand Down
6 changes: 5 additions & 1 deletion packages/core/test/application-config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ describe('ApplicationConfig', () => {
it('should set global path options', () => {
const options: GlobalPrefixOptions<ExcludeRouteMetadata> = {
exclude: [
{ pathRegex: new RegExp(/health/), requestMethod: RequestMethod.GET },
{
path: '/health',
pathRegex: new RegExp(/health/),
requestMethod: RequestMethod.GET,
},
],
};
appConfig.setGlobalPrefixOptions(options);
Expand Down