mirror of
https://github.com/nestjs/nest.git
synced 2026-10-28 09:45:43 +08:00
fix(express): apply middleware to controller root routes in strict mode
With the "strict routing" setting, the router serves the root route of a controller without a trailing slash ("/users"), while the middleware of forRoutes() was registered with one ("/users/") and did not run for it. The path is now registered without the trailing slash too.
This commit is contained in:
2 files changed
+114
-4
No files matched your search
@@ -0,0 +1,100 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
INestApplication,
|
||||
MiddlewareConsumer,
|
||||
Module,
|
||||
NestModule,
|
||||
RequestMethod,
|
||||
} from '@nestjs/common';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { ExpressAdapter } from '@nestjs/platform-express';
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
import { listenOnLoopback } from '../../_support/listen-on-loopback.js';
|
||||
|
||||
let middlewareCalls = 0;
|
||||
|
||||
@Controller('health')
|
||||
class HealthController {
|
||||
@Get()
|
||||
root() {
|
||||
return 'root';
|
||||
}
|
||||
|
||||
@Get('deep/')
|
||||
deep() {
|
||||
return 'deep';
|
||||
}
|
||||
}
|
||||
|
||||
@Controller('open')
|
||||
class OpenController {
|
||||
@Get()
|
||||
root() {
|
||||
return 'open';
|
||||
}
|
||||
}
|
||||
|
||||
@Module({ controllers: [HealthController, OpenController] })
|
||||
class AppModule implements NestModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer
|
||||
.apply((req, res, next) => {
|
||||
middlewareCalls++;
|
||||
res.statusCode = 403;
|
||||
res.end('blocked');
|
||||
})
|
||||
.exclude({ path: 'open', method: RequestMethod.GET })
|
||||
.forRoutes(HealthController, OpenController);
|
||||
}
|
||||
}
|
||||
|
||||
describe.each([[''], ['api']])(
|
||||
'Middleware with strict routing (global prefix "%s")',
|
||||
prefix => {
|
||||
let app: INestApplication;
|
||||
const base = prefix ? `/${prefix}` : '';
|
||||
|
||||
beforeAll(async () => {
|
||||
const server = express();
|
||||
server.set('strict routing', true);
|
||||
app = await NestFactory.create(AppModule, new ExpressAdapter(server), {
|
||||
logger: false,
|
||||
});
|
||||
if (prefix) {
|
||||
app.setGlobalPrefix(prefix);
|
||||
}
|
||||
await listenOnLoopback(app);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
middlewareCalls = 0;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it('should run the middleware for the root route of a controller', async () => {
|
||||
await request(app.getHttpServer())
|
||||
.get(`${base}/health`)
|
||||
.expect(403, 'blocked');
|
||||
expect(middlewareCalls).toBe(1);
|
||||
});
|
||||
|
||||
it('should run the middleware for a route declared with a trailing slash', async () => {
|
||||
await request(app.getHttpServer())
|
||||
.get(`${base}/health/deep`)
|
||||
.expect(403, 'blocked');
|
||||
expect(middlewareCalls).toBe(1);
|
||||
});
|
||||
|
||||
it('should still skip the middleware for an excluded root route', async () => {
|
||||
await request(app.getHttpServer())
|
||||
.get(`${base}/open`)
|
||||
.expect(200, 'open');
|
||||
expect(middlewareCalls).toBe(0);
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -425,10 +425,20 @@ export class ExpressAdapter extends AbstractHttpAdapter<
|
||||
if (isExactPath && router === this.instance.use) {
|
||||
router = this.instance.all;
|
||||
}
|
||||
// Express routes are not strict, so "/api" also matches "/api/", which
|
||||
// the wildcard path registered next to it may match too ("/api/{*path}"
|
||||
// does, "/api/*path" does not). The core runs the middleware for the
|
||||
// first of its paths that matches a request only.
|
||||
// Express routes are not strict by default, so "/api" also matches
|
||||
// "/api/", which the wildcard path registered next to it may match too
|
||||
// ("/api/{*path}" does, "/api/*path" does not). The core runs the
|
||||
// middleware for the first of its paths that matches a request only.
|
||||
// With "strict routing", the router serves a route the core passes
|
||||
// with a trailing slash (the root route of a controller, "/users/")
|
||||
// without it ("/users"), so the path is registered without it too.
|
||||
if (
|
||||
this.instance.router?.strict &&
|
||||
convertedPath.length > 1 &&
|
||||
convertedPath.endsWith('/')
|
||||
) {
|
||||
router.call(this.instance, stripEndSlash(convertedPath), callback);
|
||||
}
|
||||
return router.call(this.instance, convertedPath, callback);
|
||||
} catch (e) {
|
||||
if (e instanceof TypeError) {
|
||||
|
||||
Reference in new issue
Block a user