@nestjs/platform-express 12.1.1 → 12.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapters/express-adapter.d.ts +14 -3
- package/adapters/express-adapter.js +87 -15
- package/adapters/utils/get-media-type-version.util.d.ts +12 -0
- package/adapters/utils/get-media-type-version.util.js +26 -0
- package/multer/interfaces/multer-options.interface.d.ts +4 -0
- package/package.json +4 -4
|
@@ -5,7 +5,7 @@ import * as http from 'http';
|
|
|
5
5
|
import * as https from 'https';
|
|
6
6
|
import { NestExpressBodyParserOptionsFor, NestExpressBodyParserType } from '../interfaces/nest-express-body-parser.interface.js';
|
|
7
7
|
import { ServeStaticOptions } from '../interfaces/serve-static-options.interface.js';
|
|
8
|
-
import { type CorsOptions, type CorsOptionsDelegate, type VersionValue } from '@nestjs/common/internal';
|
|
8
|
+
import { type CorsOptions, type CorsOptionsDelegate, type RouteInfo, type VersionValue } from '@nestjs/common/internal';
|
|
9
9
|
import type { NestApplicationOptions } from '@nestjs/common';
|
|
10
10
|
import { AbstractHttpAdapter } from '@nestjs/core';
|
|
11
11
|
type VersionedRoute = <TRequest extends Record<string, any> = any, TResponse = any>(req: TRequest, res: TResponse, next: () => void) => any;
|
|
@@ -21,7 +21,18 @@ export declare class ExpressAdapter extends AbstractHttpAdapter<http.Server | ht
|
|
|
21
21
|
private onRequestHook?;
|
|
22
22
|
private onResponseHook?;
|
|
23
23
|
constructor(instance?: any);
|
|
24
|
-
|
|
24
|
+
private runRequestHook;
|
|
25
|
+
/**
|
|
26
|
+
* Registers a request hook. Call `done()` to continue processing the request;
|
|
27
|
+
* resolving the returned promise does not continue it. Only the first
|
|
28
|
+
* completion is forwarded to Express. Errors after `done()` are logged
|
|
29
|
+
* without advancing the request again.
|
|
30
|
+
*/
|
|
31
|
+
setOnRequestHook(onRequestHook: (req: express.Request, res: express.Response, done: (err?: Error) => void) => Promise<void> | void): void;
|
|
32
|
+
/**
|
|
33
|
+
* Registers a hook to run after the response has been sent.
|
|
34
|
+
* Thrown errors and rejected promises are logged.
|
|
35
|
+
*/
|
|
25
36
|
setOnResponseHook(onResponseHook: (req: express.Request, res: express.Response) => Promise<void> | void): void;
|
|
26
37
|
reply(response: any, body: any, statusCode?: number): any;
|
|
27
38
|
status(response: any, statusCode: number): any;
|
|
@@ -29,7 +40,7 @@ export declare class ExpressAdapter extends AbstractHttpAdapter<http.Server | ht
|
|
|
29
40
|
render(response: any, view: string, options: any): any;
|
|
30
41
|
redirect(response: any, statusCode: number, url: string): any;
|
|
31
42
|
setErrorHandler(handler: Function, prefix?: string): any;
|
|
32
|
-
setNotFoundHandler(handler: Function, prefix?: string): any;
|
|
43
|
+
setNotFoundHandler(handler: Function, prefix?: string, excludedRoutes?: RouteInfo[]): any;
|
|
33
44
|
isHeadersSent(response: any): boolean;
|
|
34
45
|
getHeader(response: any, name: string): any;
|
|
35
46
|
setHeader(response: any, name: string, value: string): any;
|
|
@@ -4,10 +4,18 @@ import express from 'express';
|
|
|
4
4
|
import * as http from 'http';
|
|
5
5
|
import * as https from 'https';
|
|
6
6
|
import { pathToRegexp } from 'path-to-regexp';
|
|
7
|
+
import { finished } from 'stream';
|
|
7
8
|
import { getBodyParserOptions } from './utils/get-body-parser-options.util.js';
|
|
9
|
+
import { getMediaTypeVersion } from './utils/get-media-type-version.util.js';
|
|
8
10
|
import { addLeadingSlash, isFunction, isNil, isObject, isString, isUndefined, stripEndSlash, } from '@nestjs/common/internal';
|
|
9
11
|
import { AbstractHttpAdapter } from '@nestjs/core';
|
|
10
12
|
import { RouterMethodFactory, LegacyRouteConverter, } from '@nestjs/core/internal';
|
|
13
|
+
// Covers "application/json" and the "+json" structured syntax suffix (RFC 6839),
|
|
14
|
+
// e.g. "application/problem+json", with or without parameters.
|
|
15
|
+
function isJsonContentType(contentType) {
|
|
16
|
+
const mediaType = contentType.split(';')[0].trim().toLowerCase();
|
|
17
|
+
return (mediaType.startsWith('application/json') || mediaType.endsWith('+json'));
|
|
18
|
+
}
|
|
11
19
|
/**
|
|
12
20
|
* @publicApi
|
|
13
21
|
*/
|
|
@@ -23,21 +31,59 @@ export class ExpressAdapter extends AbstractHttpAdapter {
|
|
|
23
31
|
super(instance || express());
|
|
24
32
|
this.instance.use((req, res, next) => {
|
|
25
33
|
if (this.onResponseHook) {
|
|
26
|
-
res.on('finish', () => {
|
|
27
|
-
|
|
34
|
+
res.on('finish', async () => {
|
|
35
|
+
try {
|
|
36
|
+
await this.onResponseHook.apply(this, [req, res]);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
this.logger.error(err);
|
|
40
|
+
}
|
|
28
41
|
});
|
|
29
42
|
}
|
|
30
43
|
if (this.onRequestHook) {
|
|
31
|
-
|
|
44
|
+
this.runRequestHook(req, res, next);
|
|
32
45
|
}
|
|
33
46
|
else {
|
|
34
47
|
next();
|
|
35
48
|
}
|
|
36
49
|
});
|
|
37
50
|
}
|
|
51
|
+
runRequestHook(req, res, next) {
|
|
52
|
+
let completed = false;
|
|
53
|
+
const doneOnce = (err) => {
|
|
54
|
+
if (completed) {
|
|
55
|
+
if (err) {
|
|
56
|
+
this.logger.error(err);
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
completed = true;
|
|
61
|
+
next(err);
|
|
62
|
+
};
|
|
63
|
+
try {
|
|
64
|
+
// Not returned to Express: rejections share the once-guard with done()
|
|
65
|
+
const result = this.onRequestHook.call(this, req, res, doneOnce);
|
|
66
|
+
if (result && typeof result.then === 'function') {
|
|
67
|
+
result.then(undefined, err => doneOnce(err || new Error('Request hook failed')));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
doneOnce(err || new Error('Request hook failed'));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Registers a request hook. Call `done()` to continue processing the request;
|
|
76
|
+
* resolving the returned promise does not continue it. Only the first
|
|
77
|
+
* completion is forwarded to Express. Errors after `done()` are logged
|
|
78
|
+
* without advancing the request again.
|
|
79
|
+
*/
|
|
38
80
|
setOnRequestHook(onRequestHook) {
|
|
39
81
|
this.onRequestHook = onRequestHook;
|
|
40
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Registers a hook to run after the response has been sent.
|
|
85
|
+
* Thrown errors and rejected promises are logged.
|
|
86
|
+
*/
|
|
41
87
|
setOnResponseHook(onResponseHook) {
|
|
42
88
|
this.onResponseHook = onResponseHook;
|
|
43
89
|
}
|
|
@@ -54,13 +100,20 @@ export class ExpressAdapter extends AbstractHttpAdapter {
|
|
|
54
100
|
stream.once('error', err => {
|
|
55
101
|
body.errorHandler(err, response);
|
|
56
102
|
});
|
|
103
|
+
// pipe() leaves the source open when the client disconnects early,
|
|
104
|
+
// also before reply() runs ("close" has already fired by then)
|
|
105
|
+
finished(response, () => {
|
|
106
|
+
if (!stream.readableEnded) {
|
|
107
|
+
stream.destroy();
|
|
108
|
+
}
|
|
109
|
+
});
|
|
57
110
|
return stream
|
|
58
111
|
.pipe(response)
|
|
59
112
|
.on('error', (err) => body.errorLogger(err));
|
|
60
113
|
}
|
|
61
114
|
const responseContentType = response.getHeader('Content-Type');
|
|
62
115
|
if (typeof responseContentType === 'string' &&
|
|
63
|
-
!responseContentType
|
|
116
|
+
!isJsonContentType(responseContentType) &&
|
|
64
117
|
body?.statusCode >= HttpStatus.BAD_REQUEST) {
|
|
65
118
|
this.logger.warn("Content-Type doesn't match Reply body, you might need a custom ExceptionFilter for non-JSON responses");
|
|
66
119
|
response.setHeader('Content-Type', 'application/json');
|
|
@@ -91,13 +144,23 @@ export class ExpressAdapter extends AbstractHttpAdapter {
|
|
|
91
144
|
// root-mounted routes) still pass through the exception layer.
|
|
92
145
|
return this.use(handler);
|
|
93
146
|
}
|
|
94
|
-
setNotFoundHandler(handler, prefix) {
|
|
147
|
+
setNotFoundHandler(handler, prefix, excludedRoutes) {
|
|
95
148
|
const normalizedPrefix = this.normalizePrefix(prefix);
|
|
96
149
|
if (normalizedPrefix) {
|
|
97
150
|
this.registeredPrefixes.add(normalizedPrefix);
|
|
98
151
|
const router = express.Router();
|
|
99
152
|
router.all('*path', handler);
|
|
100
|
-
|
|
153
|
+
this.use(normalizedPrefix, router);
|
|
154
|
+
// Excluded routes live at the root, out of the prefix router's reach.
|
|
155
|
+
// Mount the handler on each exact path, for every method, so a method
|
|
156
|
+
// miss there (`POST /hello` with only `GET /hello`) still reaches the
|
|
157
|
+
// exception layer. Not `use(path, router)`: a subtree mount would claim
|
|
158
|
+
// a raw `/hello/live` added after `init()`; a wildcard exclusion covers
|
|
159
|
+
// its subtree through its own pattern.
|
|
160
|
+
for (const route of excludedRoutes ?? []) {
|
|
161
|
+
this.instance.all(addLeadingSlash(route.path), handler);
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
101
164
|
}
|
|
102
165
|
return this.use((req, res, next) => {
|
|
103
166
|
// When multiple apps share this adapter, a non-prefixed app's 404
|
|
@@ -195,10 +258,22 @@ export class ExpressAdapter extends AbstractHttpAdapter {
|
|
|
195
258
|
createMiddlewareFactory(requestMethod) {
|
|
196
259
|
return (path, callback) => {
|
|
197
260
|
try {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
261
|
+
// The core marks an exact-match path with a trailing "$" (e.g. "/api$").
|
|
262
|
+
// Express 5 reads "$" literally and "use()" matches by prefix, so strip
|
|
263
|
+
// the marker and register the path as an exact "all()" route instead.
|
|
264
|
+
const isExactPath = path.endsWith('$');
|
|
265
|
+
const convertedPath = LegacyRouteConverter.tryConvert(isExactPath ? path.slice(0, -1) : path);
|
|
266
|
+
let router = this.routerMethodFactory.get(this.instance, requestMethod);
|
|
267
|
+
if (isExactPath && router === this.instance.use) {
|
|
268
|
+
router = this.instance.all;
|
|
269
|
+
}
|
|
270
|
+
// Express routes are not strict, so "/api" also matches "/api/". That
|
|
271
|
+
// path is left to the wildcard entry registered next to it (an optional
|
|
272
|
+
// "{*path}" matches it), so the middleware doesn't run twice.
|
|
273
|
+
const handler = isExactPath
|
|
274
|
+
? (req, res, next) => req.path.endsWith('/') ? next() : callback(req, res, next)
|
|
275
|
+
: callback;
|
|
276
|
+
return router.call(this.instance, convertedPath, handler);
|
|
202
277
|
}
|
|
203
278
|
catch (e) {
|
|
204
279
|
if (e instanceof TypeError) {
|
|
@@ -308,11 +383,9 @@ export class ExpressAdapter extends AbstractHttpAdapter {
|
|
|
308
383
|
const MEDIA_TYPE_HEADER = 'Accept';
|
|
309
384
|
const acceptHeaderValue = req.headers?.[MEDIA_TYPE_HEADER] ||
|
|
310
385
|
req.headers?.[MEDIA_TYPE_HEADER.toLowerCase()];
|
|
311
|
-
const
|
|
312
|
-
? acceptHeaderValue.split(';')[1]
|
|
313
|
-
: undefined;
|
|
386
|
+
const headerVersion = getMediaTypeVersion(acceptHeaderValue, versioningOptions.key);
|
|
314
387
|
// No version was supplied
|
|
315
|
-
if (isUndefined(
|
|
388
|
+
if (isUndefined(headerVersion)) {
|
|
316
389
|
if (Array.isArray(version)) {
|
|
317
390
|
if (version.includes(VERSION_NEUTRAL)) {
|
|
318
391
|
return handler(req, res, next);
|
|
@@ -320,7 +393,6 @@ export class ExpressAdapter extends AbstractHttpAdapter {
|
|
|
320
393
|
}
|
|
321
394
|
}
|
|
322
395
|
else {
|
|
323
|
-
const headerVersion = acceptHeaderVersionParameter.split(versioningOptions.key)[1];
|
|
324
396
|
if (Array.isArray(version)) {
|
|
325
397
|
if (version.includes(headerVersion)) {
|
|
326
398
|
return handler(req, res, next);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the version carried by an `Accept` header for media type versioning,
|
|
3
|
+
* e.g. `2` for `application/json;v=2` with the `v=` key.
|
|
4
|
+
*
|
|
5
|
+
* The version parameter may follow other parameters and may belong to any
|
|
6
|
+
* media range of a list (`text/html, application/json;v=2`); the first match
|
|
7
|
+
* wins. Returns `undefined` when no parameter starts with the key.
|
|
8
|
+
*
|
|
9
|
+
* Duplicated in each platform package so that a platform package never
|
|
10
|
+
* depends on a newer `@nestjs/core` than the one installed.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getMediaTypeVersion(acceptHeader: string | string[] | undefined, key: string): string | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the version carried by an `Accept` header for media type versioning,
|
|
3
|
+
* e.g. `2` for `application/json;v=2` with the `v=` key.
|
|
4
|
+
*
|
|
5
|
+
* The version parameter may follow other parameters and may belong to any
|
|
6
|
+
* media range of a list (`text/html, application/json;v=2`); the first match
|
|
7
|
+
* wins. Returns `undefined` when no parameter starts with the key.
|
|
8
|
+
*
|
|
9
|
+
* Duplicated in each platform package so that a platform package never
|
|
10
|
+
* depends on a newer `@nestjs/core` than the one installed.
|
|
11
|
+
*/
|
|
12
|
+
export function getMediaTypeVersion(acceptHeader, key) {
|
|
13
|
+
const mediaRanges = []
|
|
14
|
+
.concat(acceptHeader ?? [])
|
|
15
|
+
.flatMap(value => value.split(','));
|
|
16
|
+
for (const mediaRange of mediaRanges) {
|
|
17
|
+
const [, ...parameters] = mediaRange.split(';');
|
|
18
|
+
for (const parameter of parameters) {
|
|
19
|
+
const trimmedParameter = parameter.trim();
|
|
20
|
+
if (trimmedParameter.startsWith(key)) {
|
|
21
|
+
return trimmedParameter.slice(key.length);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
@@ -16,6 +16,10 @@ export interface MulterLimits {
|
|
|
16
16
|
parts?: number;
|
|
17
17
|
/** For multipart forms, the max number of header key=> value pairs to parse Default: 2000(same as node's http). */
|
|
18
18
|
headerPairs?: number;
|
|
19
|
+
/** Max nesting depth of field names; `a[b][c]` has 2 levels (Default: Infinity) */
|
|
20
|
+
fieldNestingDepth?: number;
|
|
21
|
+
/** Max numeric array index accepted in field names; `a[3]` uses index 3 (Default: Infinity) */
|
|
22
|
+
fieldArrayIndexLimit?: number;
|
|
19
23
|
}
|
|
20
24
|
/**
|
|
21
25
|
* @see https://github.com/expressjs/multer
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nestjs/platform-express",
|
|
3
|
-
"version": "12.1.
|
|
3
|
+
"version": "12.1.3",
|
|
4
4
|
"description": "Nest - modern, fast, powerful node.js web framework (@platform-express)",
|
|
5
5
|
"author": "Kamil Mysliwiec",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
"tslib": "2.8.1"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@nestjs/common": "12.1.
|
|
36
|
-
"@nestjs/core": "12.1.
|
|
35
|
+
"@nestjs/common": "12.1.3",
|
|
36
|
+
"@nestjs/core": "12.1.3"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@nestjs/common": "^12.0.0",
|
|
40
40
|
"@nestjs/core": "^12.0.0"
|
|
41
41
|
},
|
|
42
|
-
"gitHead": "
|
|
42
|
+
"gitHead": "4514623279f6087808e3c8f08fbf9f37d3c17482"
|
|
43
43
|
}
|