dx-server 0.5.0 → 0.5.1
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/README.md +2 -1
- package/cjs/body.d.ts +2 -9
- package/cjs/body.js +10 -99
- package/cjs/bodyHelpers.d.ts +16 -0
- package/cjs/bodyHelpers.js +102 -0
- package/cjs/dx.d.ts +1 -2
- package/cjs/dx.js +4 -111
- package/cjs/dxHelpers.d.ts +32 -0
- package/cjs/dxHelpers.js +113 -0
- package/cjs/helpers.d.ts +2 -0
- package/cjs/helpers.js +14 -0
- package/cjs/index.d.ts +1 -1
- package/cjs/index.js +2 -4
- package/esm/body.d.ts +2 -9
- package/esm/body.js +10 -97
- package/esm/bodyHelpers.d.ts +16 -0
- package/esm/bodyHelpers.js +89 -0
- package/esm/dx.d.ts +1 -2
- package/esm/dx.js +4 -108
- package/esm/dxHelpers.d.ts +32 -0
- package/esm/dxHelpers.js +106 -0
- package/esm/helpers.d.ts +2 -0
- package/esm/helpers.js +3 -0
- package/esm/index.d.ts +1 -1
- package/esm/index.js +2 -2
- package/package.json +6 -3
package/cjs/helpers.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.queryFromReq = exports.urlEncodedFromReq = exports.textFromReq = exports.rawFromReq = exports.jsonFromReq = exports.bufferFromReq = exports.setBufferBodyDefaultOptions = exports.writeRes = void 0;
|
|
4
|
+
var dxHelpers_js_1 = require("./dxHelpers.js");
|
|
5
|
+
Object.defineProperty(exports, "writeRes", { enumerable: true, get: function () { return dxHelpers_js_1.writeRes; } });
|
|
6
|
+
var bodyHelpers_js_1 = require("./bodyHelpers.js");
|
|
7
|
+
Object.defineProperty(exports, "setBufferBodyDefaultOptions", { enumerable: true, get: function () { return bodyHelpers_js_1.setBufferBodyDefaultOptions; } });
|
|
8
|
+
Object.defineProperty(exports, "bufferFromReq", { enumerable: true, get: function () { return bodyHelpers_js_1.bufferFromReq; } });
|
|
9
|
+
Object.defineProperty(exports, "jsonFromReq", { enumerable: true, get: function () { return bodyHelpers_js_1.jsonFromReq; } });
|
|
10
|
+
Object.defineProperty(exports, "rawFromReq", { enumerable: true, get: function () { return bodyHelpers_js_1.rawFromReq; } });
|
|
11
|
+
Object.defineProperty(exports, "textFromReq", { enumerable: true, get: function () { return bodyHelpers_js_1.textFromReq; } });
|
|
12
|
+
Object.defineProperty(exports, "urlEncodedFromReq", { enumerable: true, get: function () { return bodyHelpers_js_1.urlEncodedFromReq; } });
|
|
13
|
+
Object.defineProperty(exports, "queryFromReq", { enumerable: true, get: function () { return bodyHelpers_js_1.queryFromReq; } });
|
|
14
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGVscGVycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9oZWxwZXJzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLCtDQUF1QztBQUEvQix3R0FBQSxRQUFRLE9BQUE7QUFDaEIsbURBUXlCO0FBUHhCLDZIQUFBLDJCQUEyQixPQUFBO0FBQzNCLCtHQUFBLGFBQWEsT0FBQTtBQUNiLDZHQUFBLFdBQVcsT0FBQTtBQUNYLDRHQUFBLFVBQVUsT0FBQTtBQUNWLDZHQUFBLFdBQVcsT0FBQTtBQUNYLG1IQUFBLGlCQUFpQixPQUFBO0FBQ2pCLDhHQUFBLFlBQVksT0FBQSJ9
|
package/cjs/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { getReq, getRes, setHtml, setNodeStream, setWebStream, setJson, setBuffer, setRedirect, setText } from './dx.js';
|
|
2
2
|
import { dxServer } from './dx.js';
|
|
3
|
-
export {
|
|
3
|
+
export { getJson, getRaw, getText, getUrlEncoded, getQuery, } from './body.js';
|
|
4
4
|
export { router } from './route.js';
|
|
5
5
|
export default dxServer;
|
package/cjs/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.router = exports.getQuery = exports.getUrlEncoded = exports.getText = exports.getRaw = exports.getJson = exports.
|
|
3
|
+
exports.router = exports.getQuery = exports.getUrlEncoded = exports.getText = exports.getRaw = exports.getJson = exports.setText = exports.setRedirect = exports.setBuffer = exports.setJson = exports.setWebStream = exports.setNodeStream = exports.setHtml = exports.getRes = exports.getReq = void 0;
|
|
4
4
|
var dx_js_1 = require("./dx.js");
|
|
5
5
|
Object.defineProperty(exports, "getReq", { enumerable: true, get: function () { return dx_js_1.getReq; } });
|
|
6
6
|
Object.defineProperty(exports, "getRes", { enumerable: true, get: function () { return dx_js_1.getRes; } });
|
|
@@ -13,8 +13,6 @@ Object.defineProperty(exports, "setRedirect", { enumerable: true, get: function
|
|
|
13
13
|
Object.defineProperty(exports, "setText", { enumerable: true, get: function () { return dx_js_1.setText; } });
|
|
14
14
|
const dx_js_2 = require("./dx.js");
|
|
15
15
|
var body_js_1 = require("./body.js");
|
|
16
|
-
Object.defineProperty(exports, "setBufferBodyDefaultOptions", { enumerable: true, get: function () { return body_js_1.setBufferBodyDefaultOptions; } });
|
|
17
|
-
Object.defineProperty(exports, "getBuffer", { enumerable: true, get: function () { return body_js_1.getBuffer; } });
|
|
18
16
|
Object.defineProperty(exports, "getJson", { enumerable: true, get: function () { return body_js_1.getJson; } });
|
|
19
17
|
Object.defineProperty(exports, "getRaw", { enumerable: true, get: function () { return body_js_1.getRaw; } });
|
|
20
18
|
Object.defineProperty(exports, "getText", { enumerable: true, get: function () { return body_js_1.getText; } });
|
|
@@ -23,4 +21,4 @@ Object.defineProperty(exports, "getQuery", { enumerable: true, get: function ()
|
|
|
23
21
|
var route_js_1 = require("./route.js");
|
|
24
22
|
Object.defineProperty(exports, "router", { enumerable: true, get: function () { return route_js_1.router; } });
|
|
25
23
|
exports.default = dx_js_2.dxServer;
|
|
26
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
24
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsaUNBVWdCO0FBVGYsK0ZBQUEsTUFBTSxPQUFBO0FBQ04sK0ZBQUEsTUFBTSxPQUFBO0FBQ04sZ0dBQUEsT0FBTyxPQUFBO0FBQ1Asc0dBQUEsYUFBYSxPQUFBO0FBQ2IscUdBQUEsWUFBWSxPQUFBO0FBQ1osZ0dBQUEsT0FBTyxPQUFBO0FBQ1Asa0dBQUEsU0FBUyxPQUFBO0FBQ1Qsb0dBQUEsV0FBVyxPQUFBO0FBQ1gsZ0dBQUEsT0FBTyxPQUFBO0FBRVIsbUNBQWdDO0FBQ2hDLHFDQU1rQjtBQUxqQixrR0FBQSxPQUFPLE9BQUE7QUFDUCxpR0FBQSxNQUFNLE9BQUE7QUFDTixrR0FBQSxPQUFPLE9BQUE7QUFDUCx3R0FBQSxhQUFhLE9BQUE7QUFDYixtR0FBQSxRQUFRLE9BQUE7QUFFVCx1Q0FBaUM7QUFBekIsa0dBQUEsTUFBTSxPQUFBO0FBRWQsa0JBQWUsZ0JBQVEsQ0FBQSJ9
|
package/esm/body.d.ts
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
limit: number;
|
|
3
|
-
}
|
|
4
|
-
export declare function setBufferBodyDefaultOptions(options: Partial<BufferBodyOptions>): void;
|
|
5
|
-
export declare function getBuffer(options?: Partial<BufferBodyOptions>): Promise<any>;
|
|
1
|
+
import { BufferBodyOptions } from './bodyHelpers.js';
|
|
6
2
|
export declare function getJson(options?: Partial<BufferBodyOptions>): Promise<any>;
|
|
7
3
|
export declare function getRaw(options?: Partial<BufferBodyOptions>): Promise<any>;
|
|
8
4
|
export declare function getText(options?: Partial<BufferBodyOptions>): Promise<any>;
|
|
9
|
-
export declare function getUrlEncoded(
|
|
10
|
-
simplify?: boolean;
|
|
11
|
-
}): Promise<any>;
|
|
5
|
+
export declare function getUrlEncoded(options: Partial<BufferBodyOptions>): Promise<any>;
|
|
12
6
|
export declare function getQuery({ simplify, ...options }?: Partial<BufferBodyOptions> & {
|
|
13
7
|
simplify?: boolean;
|
|
14
8
|
}): Promise<any>;
|
|
15
|
-
export {};
|
package/esm/body.js
CHANGED
|
@@ -1,114 +1,27 @@
|
|
|
1
|
-
import { getContentStream, readStream } from './stream.js';
|
|
2
|
-
import { parse } from 'qs';
|
|
3
|
-
import { parseContentType } from './contentType.js';
|
|
4
1
|
import { getReq } from './dx.js';
|
|
5
|
-
|
|
6
|
-
export function setBufferBodyDefaultOptions(options) {
|
|
7
|
-
bufferBodyDefaultOptions = { ...bufferBodyDefaultOptions, ...options };
|
|
8
|
-
}
|
|
2
|
+
import { bufferFromReq, jsonFromReq, queryFromReq, rawFromReq, textFromReq, urlEncodedFromReq } from './bodyHelpers.js';
|
|
9
3
|
const bufferBodySymbol = Symbol('bufferBody');
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const req = getReq();
|
|
13
|
-
return req[bufferBodySymbol] ??= (async () => {
|
|
14
|
-
/**
|
|
15
|
-
* Check if a request has a request body.
|
|
16
|
-
* A request with a body __must__ either have `transfer-encoding`
|
|
17
|
-
* or `content-length` headers set.
|
|
18
|
-
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
|
|
19
|
-
*/
|
|
20
|
-
// https://github.com/jshttp/type-is/blob/cdcfe23e9833872e425b0aaf71ca0311373b6116/index.js#L92
|
|
21
|
-
const contentLengthParsed = parseInt(req.headers['content-length'] ?? '', 10);
|
|
22
|
-
if (req.headers['transfer-encoding'] === undefined
|
|
23
|
-
&& isNaN(contentLengthParsed))
|
|
24
|
-
return;
|
|
25
|
-
const contentLength = isNaN(contentLengthParsed) ? undefined : contentLengthParsed;
|
|
26
|
-
// read
|
|
27
|
-
const encoding = (req.headers['content-encoding'] ?? 'identity').toLowerCase();
|
|
28
|
-
const stream = getContentStream(req, encoding);
|
|
29
|
-
return await readStream(stream, {
|
|
30
|
-
length: encoding === 'identity' ? contentLength : undefined,
|
|
31
|
-
limit,
|
|
32
|
-
});
|
|
33
|
-
})();
|
|
34
|
-
}
|
|
35
|
-
// if content-type is not as expected, return undefined
|
|
36
|
-
function forceGetContentTypeParams(expected) {
|
|
37
|
-
const req = getReq();
|
|
38
|
-
const contentTypeRaw = req.headers['content-type'];
|
|
39
|
-
if (!contentTypeRaw)
|
|
40
|
-
return;
|
|
41
|
-
const { mediaType, parameters } = parseContentType(contentTypeRaw);
|
|
42
|
-
if (mediaType !== expected)
|
|
43
|
-
return;
|
|
44
|
-
return parameters;
|
|
45
|
-
}
|
|
46
|
-
function forceGetCharset(expected) {
|
|
47
|
-
const parameters = forceGetContentTypeParams(expected);
|
|
48
|
-
if (!parameters)
|
|
49
|
-
return;
|
|
50
|
-
// assert charset per RFC 7159 sec 8.1
|
|
51
|
-
const charset = parameters.charset?.toLowerCase() || 'utf-8';
|
|
52
|
-
if (!charset.startsWith('utf-'))
|
|
53
|
-
throw new Error(`unsupported charset "${charset.toUpperCase()}"`);
|
|
54
|
-
return charset;
|
|
4
|
+
async function getBuffer(options) {
|
|
5
|
+
return getReq()[bufferBodySymbol] ??= bufferFromReq(getReq(), options);
|
|
55
6
|
}
|
|
56
7
|
const jsonBodySymbol = Symbol('jsonBody');
|
|
57
8
|
export async function getJson(options) {
|
|
58
|
-
return getReq()[jsonBodySymbol] ??= (
|
|
59
|
-
const charset = forceGetCharset('application/json');
|
|
60
|
-
if (!charset)
|
|
61
|
-
return;
|
|
62
|
-
const buffer = await getBuffer(options);
|
|
63
|
-
if (buffer) {
|
|
64
|
-
const str = buffer.toString(charset);
|
|
65
|
-
return str ? JSON.parse(str) : undefined;
|
|
66
|
-
}
|
|
67
|
-
})();
|
|
9
|
+
return getReq()[jsonBodySymbol] ??= jsonFromReq(getReq(), options);
|
|
68
10
|
}
|
|
69
11
|
const rawBodySymbol = Symbol('rawBody');
|
|
70
12
|
export async function getRaw(options) {
|
|
71
|
-
return getReq()[rawBodySymbol] ??= (
|
|
72
|
-
if (!forceGetContentTypeParams('application/octet-stream'))
|
|
73
|
-
return;
|
|
74
|
-
return await getBuffer(options);
|
|
75
|
-
})();
|
|
13
|
+
return getReq()[rawBodySymbol] ??= rawFromReq(getReq(), options);
|
|
76
14
|
}
|
|
77
15
|
const textBodySymbol = Symbol('textBody');
|
|
78
16
|
export async function getText(options) {
|
|
79
|
-
return getReq()[textBodySymbol] ??= (
|
|
80
|
-
const charset = forceGetCharset('text/plain');
|
|
81
|
-
if (!charset)
|
|
82
|
-
return;
|
|
83
|
-
const buffer = await getBuffer(options);
|
|
84
|
-
if (buffer)
|
|
85
|
-
return buffer.toString(charset);
|
|
86
|
-
})();
|
|
17
|
+
return getReq()[textBodySymbol] ??= textFromReq(getReq(), options);
|
|
87
18
|
}
|
|
88
19
|
const urlEncodedBodySymbol = Symbol('urlencodedBody');
|
|
89
|
-
export async function getUrlEncoded(
|
|
90
|
-
return getReq()[urlEncodedBodySymbol] ??= (
|
|
91
|
-
const charset = forceGetCharset('application/x-www-form-urlencoded');
|
|
92
|
-
if (!charset)
|
|
93
|
-
return;
|
|
94
|
-
const buffer = await getBuffer(options);
|
|
95
|
-
if (buffer) {
|
|
96
|
-
const str = buffer.toString(charset);
|
|
97
|
-
return simplify
|
|
98
|
-
? Object.fromEntries(new URLSearchParams(str))
|
|
99
|
-
: parse(str);
|
|
100
|
-
}
|
|
101
|
-
})();
|
|
20
|
+
export async function getUrlEncoded(options) {
|
|
21
|
+
return getReq()[urlEncodedBodySymbol] ??= urlEncodedFromReq(getReq(), options);
|
|
102
22
|
}
|
|
103
23
|
const querySymbol = Symbol('query');
|
|
104
24
|
export async function getQuery({ simplify, ...options } = {}) {
|
|
105
|
-
return getReq()[querySymbol] ??= (
|
|
106
|
-
const query = getReq().url?.split('?', 2)?.[1];
|
|
107
|
-
return query
|
|
108
|
-
? simplify
|
|
109
|
-
? Object.fromEntries(new URLSearchParams(query))
|
|
110
|
-
: parse(query)
|
|
111
|
-
: {};
|
|
112
|
-
})();
|
|
25
|
+
return getReq()[querySymbol] ??= queryFromReq(getReq(), options);
|
|
113
26
|
}
|
|
114
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
27
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYm9keS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9ib2R5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBQyxNQUFNLEVBQUMsTUFBTSxTQUFTLENBQUE7QUFDOUIsT0FBTyxFQUVOLGFBQWEsRUFDYixXQUFXLEVBQ1gsWUFBWSxFQUNaLFVBQVUsRUFDVixXQUFXLEVBQ1gsaUJBQWlCLEVBQ2pCLE1BQU0sa0JBQWtCLENBQUE7QUFFekIsTUFBTSxnQkFBZ0IsR0FBRyxNQUFNLENBQUMsWUFBWSxDQUFDLENBQUE7QUFDN0MsS0FBSyxVQUFVLFNBQVMsQ0FBQyxPQUFvQztJQUM1RCxPQUFPLE1BQU0sRUFBRSxDQUFDLGdCQUFnQixDQUFDLEtBQUssYUFBYSxDQUFDLE1BQU0sRUFBRSxFQUFFLE9BQU8sQ0FBQyxDQUFBO0FBQ3ZFLENBQUM7QUFFRCxNQUFNLGNBQWMsR0FBRyxNQUFNLENBQUMsVUFBVSxDQUFDLENBQUE7QUFDekMsTUFBTSxDQUFDLEtBQUssVUFBVSxPQUFPLENBQUMsT0FBb0M7SUFDakUsT0FBTyxNQUFNLEVBQUUsQ0FBQyxjQUFjLENBQUMsS0FBSyxXQUFXLENBQUMsTUFBTSxFQUFFLEVBQUUsT0FBTyxDQUFDLENBQUE7QUFDbkUsQ0FBQztBQUVELE1BQU0sYUFBYSxHQUFHLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQTtBQUN2QyxNQUFNLENBQUMsS0FBSyxVQUFVLE1BQU0sQ0FBQyxPQUFvQztJQUNoRSxPQUFPLE1BQU0sRUFBRSxDQUFDLGFBQWEsQ0FBQyxLQUFLLFVBQVUsQ0FBQyxNQUFNLEVBQUUsRUFBRSxPQUFPLENBQUMsQ0FBQTtBQUNqRSxDQUFDO0FBRUQsTUFBTSxjQUFjLEdBQUcsTUFBTSxDQUFDLFVBQVUsQ0FBQyxDQUFBO0FBQ3pDLE1BQU0sQ0FBQyxLQUFLLFVBQVUsT0FBTyxDQUFDLE9BQW9DO0lBQ2pFLE9BQU8sTUFBTSxFQUFFLENBQUMsY0FBYyxDQUFDLEtBQUssV0FBVyxDQUFDLE1BQU0sRUFBRSxFQUFFLE9BQU8sQ0FBQyxDQUFBO0FBQ25FLENBQUM7QUFFRCxNQUFNLG9CQUFvQixHQUFHLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFBO0FBQ3JELE1BQU0sQ0FBQyxLQUFLLFVBQVUsYUFBYSxDQUFDLE9BQW1DO0lBQ3RFLE9BQU8sTUFBTSxFQUFFLENBQUMsb0JBQW9CLENBQUMsS0FBSyxpQkFBaUIsQ0FBQyxNQUFNLEVBQUUsRUFBRSxPQUFPLENBQUMsQ0FBQTtBQUMvRSxDQUFDO0FBRUQsTUFBTSxXQUFXLEdBQUcsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFBO0FBQ25DLE1BQU0sQ0FBQyxLQUFLLFVBQVUsUUFBUSxDQUFDLEVBQUMsUUFBUSxFQUFFLEdBQUcsT0FBTyxLQUF1RCxFQUFFO0lBQzVHLE9BQU8sTUFBTSxFQUFFLENBQUMsV0FBVyxDQUFDLEtBQUssWUFBWSxDQUFDLE1BQU0sRUFBRSxFQUFFLE9BQU8sQ0FBQyxDQUFBO0FBQ2pFLENBQUMifQ==
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
3
|
+
import { IncomingMessage } from 'node:http';
|
|
4
|
+
import qs from 'qs';
|
|
5
|
+
export interface BufferBodyOptions {
|
|
6
|
+
bodyLimit: number;
|
|
7
|
+
simplifyUrlEncodedBody?: boolean;
|
|
8
|
+
simplifyQuery?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function setBufferBodyDefaultOptions(options: Partial<BufferBodyOptions>): void;
|
|
11
|
+
export declare function bufferFromReq(req: IncomingMessage, options?: Partial<BufferBodyOptions>): Promise<Buffer | undefined>;
|
|
12
|
+
export declare function jsonFromReq(req: IncomingMessage, options?: Partial<BufferBodyOptions>): Promise<any>;
|
|
13
|
+
export declare function rawFromReq(req: IncomingMessage, options?: Partial<BufferBodyOptions>): Promise<Buffer | undefined>;
|
|
14
|
+
export declare function textFromReq(req: IncomingMessage, options?: Partial<BufferBodyOptions>): Promise<string | undefined>;
|
|
15
|
+
export declare function urlEncodedFromReq(req: IncomingMessage, options?: Partial<BufferBodyOptions>): Promise<qs.ParsedQs | undefined>;
|
|
16
|
+
export declare function queryFromReq(req: IncomingMessage, options?: Partial<BufferBodyOptions>): Promise<qs.ParsedQs>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { getContentStream, readStream } from './stream.js';
|
|
2
|
+
import { parseContentType } from './contentType.js';
|
|
3
|
+
import qs from 'qs';
|
|
4
|
+
let bodyDefaultOptions = { bodyLimit: 100 << 10 }; // 100kb
|
|
5
|
+
export function setBufferBodyDefaultOptions(options) {
|
|
6
|
+
bodyDefaultOptions = { ...bodyDefaultOptions, ...options };
|
|
7
|
+
}
|
|
8
|
+
export async function bufferFromReq(req, options) {
|
|
9
|
+
const { bodyLimit } = { ...bodyDefaultOptions, ...options };
|
|
10
|
+
/**
|
|
11
|
+
* Check if a request has a request body.
|
|
12
|
+
* A request with a body __must__ either have `transfer-encoding`
|
|
13
|
+
* or `content-length` headers set.
|
|
14
|
+
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
|
|
15
|
+
*/
|
|
16
|
+
// https://github.com/jshttp/type-is/blob/cdcfe23e9833872e425b0aaf71ca0311373b6116/index.js#L92
|
|
17
|
+
const contentLengthParsed = parseInt(req.headers['content-length'] ?? '', 10);
|
|
18
|
+
if (req.headers['transfer-encoding'] === undefined
|
|
19
|
+
&& isNaN(contentLengthParsed))
|
|
20
|
+
return;
|
|
21
|
+
const contentLength = isNaN(contentLengthParsed) ? undefined : contentLengthParsed;
|
|
22
|
+
// read
|
|
23
|
+
const encoding = (req.headers['content-encoding'] ?? 'identity').toLowerCase();
|
|
24
|
+
const stream = getContentStream(req, encoding);
|
|
25
|
+
return await readStream(stream, {
|
|
26
|
+
length: encoding === 'identity' ? contentLength : undefined,
|
|
27
|
+
limit: bodyLimit,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
// if content-type is not as expected, return undefined
|
|
31
|
+
function forceGetContentTypeParams(req, expected) {
|
|
32
|
+
const contentTypeRaw = req.headers['content-type'];
|
|
33
|
+
if (!contentTypeRaw)
|
|
34
|
+
return;
|
|
35
|
+
const { mediaType, parameters } = parseContentType(contentTypeRaw);
|
|
36
|
+
if (mediaType !== expected)
|
|
37
|
+
return;
|
|
38
|
+
return parameters;
|
|
39
|
+
}
|
|
40
|
+
function forceGetCharset(req, expected) {
|
|
41
|
+
const parameters = forceGetContentTypeParams(req, expected);
|
|
42
|
+
if (!parameters)
|
|
43
|
+
return;
|
|
44
|
+
// assert charset per RFC 7159 sec 8.1
|
|
45
|
+
const charset = parameters.charset?.toLowerCase() || 'utf-8';
|
|
46
|
+
if (!charset.startsWith('utf-'))
|
|
47
|
+
throw new Error(`unsupported charset "${charset.toUpperCase()}"`);
|
|
48
|
+
return charset;
|
|
49
|
+
}
|
|
50
|
+
export async function jsonFromReq(req, options) {
|
|
51
|
+
const charset = forceGetCharset(req, 'application/json');
|
|
52
|
+
if (!charset)
|
|
53
|
+
return;
|
|
54
|
+
const buffer = await bufferFromReq(req, options);
|
|
55
|
+
if (buffer) {
|
|
56
|
+
const str = buffer.toString(charset);
|
|
57
|
+
return str ? JSON.parse(str) : undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export async function rawFromReq(req, options) {
|
|
61
|
+
if (!forceGetContentTypeParams(req, 'application/octet-stream'))
|
|
62
|
+
return;
|
|
63
|
+
return await bufferFromReq(req, options);
|
|
64
|
+
}
|
|
65
|
+
export async function textFromReq(req, options) {
|
|
66
|
+
const charset = forceGetCharset(req, 'text/plain');
|
|
67
|
+
if (!charset)
|
|
68
|
+
return;
|
|
69
|
+
const buffer = await bufferFromReq(req, options);
|
|
70
|
+
if (buffer)
|
|
71
|
+
return buffer.toString(charset);
|
|
72
|
+
}
|
|
73
|
+
export async function urlEncodedFromReq(req, options) {
|
|
74
|
+
const charset = forceGetCharset(req, 'application/x-www-form-urlencoded');
|
|
75
|
+
if (!charset)
|
|
76
|
+
return;
|
|
77
|
+
const buffer = await bufferFromReq(req, options);
|
|
78
|
+
if (buffer) {
|
|
79
|
+
const str = buffer.toString(charset);
|
|
80
|
+
const { simplifyUrlEncodedBody } = { ...bodyDefaultOptions, ...options };
|
|
81
|
+
return simplifyUrlEncodedBody ? Object.fromEntries(new URLSearchParams(str)) : qs.parse(str);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export async function queryFromReq(req, options) {
|
|
85
|
+
const query = new URL(req.url ?? '', 'https://example.com').searchParams.toString();
|
|
86
|
+
const { simplifyQuery } = { ...bodyDefaultOptions, ...options };
|
|
87
|
+
return simplifyQuery ? Object.fromEntries(new URLSearchParams(query)) : qs.parse(query);
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYm9keUhlbHBlcnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvYm9keUhlbHBlcnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxFQUFDLGdCQUFnQixFQUFFLFVBQVUsRUFBQyxNQUFNLGFBQWEsQ0FBQTtBQUN4RCxPQUFPLEVBQUMsZ0JBQWdCLEVBQUMsTUFBTSxrQkFBa0IsQ0FBQTtBQUNqRCxPQUFPLEVBQUUsTUFBTSxJQUFJLENBQUE7QUFRbkIsSUFBSSxrQkFBa0IsR0FBc0IsRUFBQyxTQUFTLEVBQUUsR0FBRyxJQUFJLEVBQUUsRUFBQyxDQUFBLENBQUMsUUFBUTtBQUMzRSxNQUFNLFVBQVUsMkJBQTJCLENBQUMsT0FBbUM7SUFDOUUsa0JBQWtCLEdBQUcsRUFBQyxHQUFHLGtCQUFrQixFQUFFLEdBQUcsT0FBTyxFQUFDLENBQUE7QUFDekQsQ0FBQztBQUVELE1BQU0sQ0FBQyxLQUFLLFVBQVUsYUFBYSxDQUFDLEdBQW9CLEVBQUUsT0FBb0M7SUFDN0YsTUFBTSxFQUFDLFNBQVMsRUFBQyxHQUFHLEVBQUMsR0FBRyxrQkFBa0IsRUFBRSxHQUFHLE9BQU8sRUFBQyxDQUFBO0lBQ3ZEOzs7OztPQUtHO0lBQ0YsK0ZBQStGO0lBQ2hHLE1BQU0sbUJBQW1CLEdBQUcsUUFBUSxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUE7SUFDN0UsSUFDQyxHQUFHLENBQUMsT0FBTyxDQUFDLG1CQUFtQixDQUFDLEtBQUssU0FBUztXQUMzQyxLQUFLLENBQUMsbUJBQW1CLENBQUM7UUFDNUIsT0FBTTtJQUNSLE1BQU0sYUFBYSxHQUFHLEtBQUssQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLG1CQUFtQixDQUFBO0lBRWxGLE9BQU87SUFDUCxNQUFNLFFBQVEsR0FBRyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENB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
|
package/esm/dx.d.ts
CHANGED
|
@@ -27,9 +27,8 @@ export declare function setNodeStream(stream: Readable, { status }?: {
|
|
|
27
27
|
export declare function setWebStream(stream: ReadableStream, { status }?: {
|
|
28
28
|
status?: number;
|
|
29
29
|
}): void;
|
|
30
|
-
export declare function setJson(json: any, { status
|
|
30
|
+
export declare function setJson(json: any, { status }?: {
|
|
31
31
|
status?: number;
|
|
32
|
-
beautify?: boolean;
|
|
33
32
|
}): void;
|
|
34
33
|
export declare function setRedirect(url: string, status: 301 | 302): void;
|
|
35
34
|
export {};
|
package/esm/dx.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import makeDefer from 'jdefer';
|
|
2
|
-
import { promisify } from 'node:util';
|
|
3
|
-
import { entityTag, isFreshETag } from './etag.js';
|
|
4
|
-
import { Readable } from 'node:stream';
|
|
5
1
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { writeRes } from './dxHelpers.js';
|
|
6
3
|
const reqStorage = new AsyncLocalStorage();
|
|
7
4
|
const resStorage = new AsyncLocalStorage();
|
|
8
5
|
const dxStorage = new AsyncLocalStorage();
|
|
@@ -10,106 +7,7 @@ export function dxServer(req, res, options = {}) {
|
|
|
10
7
|
return async (next) => {
|
|
11
8
|
const dx = { ...options };
|
|
12
9
|
const result = await dxStorage.run(dx, () => reqStorage.run(req, () => resStorage.run(res, next)));
|
|
13
|
-
|
|
14
|
-
const setContentType = (contentType) => {
|
|
15
|
-
if (res.headersSent || res.getHeader('content-type'))
|
|
16
|
-
return;
|
|
17
|
-
res.setHeader('content-type', `${contentType}${charset ? `; charset=${charset}` : ''}`);
|
|
18
|
-
};
|
|
19
|
-
let bufferOrStream;
|
|
20
|
-
switch (type) {
|
|
21
|
-
case 'text':
|
|
22
|
-
setContentType('text/plain');
|
|
23
|
-
case 'html':
|
|
24
|
-
setContentType('text/html');
|
|
25
|
-
// shared with text
|
|
26
|
-
bufferOrStream = Buffer.from(data, charset);
|
|
27
|
-
break;
|
|
28
|
-
case 'buffer':
|
|
29
|
-
setContentType('application/octet-stream');
|
|
30
|
-
bufferOrStream = data;
|
|
31
|
-
break;
|
|
32
|
-
case 'nodeStream':
|
|
33
|
-
setContentType('application/octet-stream');
|
|
34
|
-
bufferOrStream = data;
|
|
35
|
-
break;
|
|
36
|
-
case 'webStream':
|
|
37
|
-
setContentType('application/octet-stream');
|
|
38
|
-
bufferOrStream = Readable.fromWeb(data);
|
|
39
|
-
break;
|
|
40
|
-
case 'json':
|
|
41
|
-
setContentType('application/json');
|
|
42
|
-
bufferOrStream = Buffer.from(jsonBeautify ? JSON.stringify(data, null, 2) : JSON.stringify(data), charset);
|
|
43
|
-
break;
|
|
44
|
-
case 'redirect':
|
|
45
|
-
res.setHeader('location', data);
|
|
46
|
-
bufferOrStream = Buffer.from('', charset);
|
|
47
|
-
break;
|
|
48
|
-
case undefined:
|
|
49
|
-
// skip response. Some middleware may handle it outside the chain. For example, express middleware
|
|
50
|
-
return result;
|
|
51
|
-
default:
|
|
52
|
-
if (!res.getHeader('content-type'))
|
|
53
|
-
res.setHeader('content-type', 'text/plain');
|
|
54
|
-
throw new Error(`unsupported response type ${type}`);
|
|
55
|
-
}
|
|
56
|
-
if (res.headersSent) {
|
|
57
|
-
if (res.writableFinished) {
|
|
58
|
-
// skipped: response is already finished
|
|
59
|
-
}
|
|
60
|
-
else if (res.writableEnded) {
|
|
61
|
-
const defer = makeDefer();
|
|
62
|
-
res.addListener('finish', defer.resolve);
|
|
63
|
-
await defer.promise;
|
|
64
|
-
// skipped: response is already ended
|
|
65
|
-
// chunk is not fully flushed yet
|
|
66
|
-
}
|
|
67
|
-
else
|
|
68
|
-
await promisify(res.end.bind(res))(undefined); // to be consistent, we end the response immediately
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
// https://github.com/expressjs/express/blob/980d881e3b023db079de60477a2588a91f046ca5/lib/response.js#L210
|
|
72
|
-
if (res.statusCode === 204) { // No Content
|
|
73
|
-
res.removeHeader('content-type');
|
|
74
|
-
res.removeHeader('content-length');
|
|
75
|
-
res.removeHeader('transfer-encoding');
|
|
76
|
-
// write nothing
|
|
77
|
-
}
|
|
78
|
-
if (res.statusCode === 205) { // reset content. Tell client to clear the form, etc.
|
|
79
|
-
res.setHeader('content-length', 0);
|
|
80
|
-
res.removeHeader('transfer-encoding');
|
|
81
|
-
}
|
|
82
|
-
else if (req.method === 'HEAD') {
|
|
83
|
-
// write nothing
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
if (Buffer.isBuffer(bufferOrStream)) {
|
|
87
|
-
// support: 304 (etag), zipping, file etag and last modified
|
|
88
|
-
res.setHeader('content-length', bufferOrStream.length);
|
|
89
|
-
if (!disableEtag) {
|
|
90
|
-
const etag = entityTag(bufferOrStream);
|
|
91
|
-
const lastModified = res.getHeader('last-modified');
|
|
92
|
-
res.setHeader('ETag', etag);
|
|
93
|
-
if (isFreshETag(req, etag)) {
|
|
94
|
-
res.removeHeader('content-type');
|
|
95
|
-
res.removeHeader('content-length');
|
|
96
|
-
res.removeHeader('transfer-encoding');
|
|
97
|
-
res.statusCode = 304;
|
|
98
|
-
// write nothing
|
|
99
|
-
}
|
|
100
|
-
else
|
|
101
|
-
res.write(bufferOrStream);
|
|
102
|
-
}
|
|
103
|
-
else
|
|
104
|
-
res.write(bufferOrStream);
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
bufferOrStream.pipe(res);
|
|
108
|
-
}
|
|
109
|
-
// we do not support content-encoding (gzip, deflate, br) and leave it to reverse proxy or CDN
|
|
110
|
-
}
|
|
111
|
-
await promisify(res.end.bind(res))(undefined); // some express middleware, such as express-session, requires explicitly passing chunk
|
|
112
|
-
}
|
|
10
|
+
await writeRes(req, res, dx);
|
|
113
11
|
return result;
|
|
114
12
|
};
|
|
115
13
|
}
|
|
@@ -161,15 +59,13 @@ export function setWebStream(stream, { status } = {}) {
|
|
|
161
59
|
dx.data = stream;
|
|
162
60
|
dx.type = 'webStream';
|
|
163
61
|
}
|
|
164
|
-
export function setJson(json, { status
|
|
62
|
+
export function setJson(json, { status } = {}) {
|
|
165
63
|
const res = getRes();
|
|
166
64
|
if (status)
|
|
167
65
|
res.statusCode = status;
|
|
168
66
|
const dx = dxStorage.getStore();
|
|
169
67
|
dx.data = json;
|
|
170
68
|
dx.type = 'json';
|
|
171
|
-
if (beautify !== undefined)
|
|
172
|
-
dx.jsonBeautify = beautify;
|
|
173
69
|
}
|
|
174
70
|
export function setRedirect(url, status) {
|
|
175
71
|
const res = getRes();
|
|
@@ -178,4 +74,4 @@ export function setRedirect(url, status) {
|
|
|
178
74
|
dx.data = url;
|
|
179
75
|
dx.type = 'redirect';
|
|
180
76
|
}
|
|
181
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
77
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZHguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvZHgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUEsT0FBTyxFQUFDLGlCQUFpQixFQUFDLE1BQU0sa0JBQWtCLENBQUE7QUFDbEQsT0FBTyxFQUFpQixRQUFRLEVBQUMsTUFBTSxnQkFBZ0IsQ0FBQTtBQVV2RCxNQUFNLFVBQVUsR0FBRyxJQUFJLGlCQUFpQixFQUFtQixDQUFBO0FBQzNELE1BQU0sVUFBVSxHQUFHLElBQUksaUJBQWlCLEVBQWtCLENBQUE7QUFDMUQsTUFBTSxTQUFTLEdBQUcsSUFBSSxpQkFBaUIsRUFBYSxDQUFBO0FBQ3BELE1BQU0sVUFBVSxRQUFRLENBQ3ZCLEdBQW9CLEVBQ3BCLEdBQW1CLEVBQ25CLFVBR0ksRUFBRTtJQUVOLE9BQU8sS0FBSyxFQUFDLElBQUksRUFBQyxFQUFFO1FBQ25CLE1BQU0sRUFBRSxHQUFjLEVBQUMsR0FBRyxPQUFPLEVBQUMsQ0FBQTtRQUNsQyxNQUFNLE1BQU0sR0FBRyxNQUFNLFNBQVMsQ0FBQyxHQUFHLENBQ2pDLEVBQUUsRUFDRixHQUFHLEVBQUUsQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUNuQixHQUFHLEVBQ0gsR0FBRyxFQUFFLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxHQUFHLEVBQUUsSUFBSSxDQUFDLENBQy9CLENBQ0QsQ0FBQTtRQUNELE1BQU0sUUFBUSxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsRUFBRSxDQUFDLENBQUE7UUFDNUIsT0FBTyxNQUFNLENBQUE7SUFDZCxDQUFDLENBQUE7QUFDRixDQUFDO0FBRUQsZUFBZTtBQUNmLGdEQUFnRDtBQUNoRCwyRkFBMkY7QUFDM0YsZ0ZBQWdGO0FBQ2hGLE1BQU0sVUFBVSxNQUFNO0lBQ3JCLE9BQU8sVUFBVSxDQUFDLFFBQVEsRUFBRyxDQUFBO0FBQzlCLENBQUM7QUFDRCxNQUFNLFVBQVUsTUFBTTtJQUNyQixPQUFPLFVBQVUsQ0FBQyxRQUFRLEVBQUcsQ0FB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
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
3
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
4
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
5
|
+
import { Readable } from 'node:stream';
|
|
6
|
+
export type DxContext = {
|
|
7
|
+
charset?: BufferEncoding;
|
|
8
|
+
jsonBeautify?: boolean;
|
|
9
|
+
disableEtag?: boolean;
|
|
10
|
+
} & ({
|
|
11
|
+
type: 'text';
|
|
12
|
+
data: string;
|
|
13
|
+
} | {
|
|
14
|
+
type: 'html';
|
|
15
|
+
data: string;
|
|
16
|
+
} | {
|
|
17
|
+
type: 'buffer';
|
|
18
|
+
data: Buffer;
|
|
19
|
+
} | {
|
|
20
|
+
type: 'json';
|
|
21
|
+
data: any;
|
|
22
|
+
} | {
|
|
23
|
+
type: 'redirect';
|
|
24
|
+
data: string;
|
|
25
|
+
} | {
|
|
26
|
+
type: 'nodeStream';
|
|
27
|
+
data: Readable;
|
|
28
|
+
} | {
|
|
29
|
+
type: 'webStream';
|
|
30
|
+
data: ReadableStream;
|
|
31
|
+
});
|
|
32
|
+
export declare function writeRes(req: IncomingMessage, res: ServerResponse, { type, data, charset, jsonBeautify, disableEtag }: DxContext): Promise<void>;
|
package/esm/dxHelpers.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
import makeDefer from 'jdefer';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import { entityTag, isFreshETag } from './etag.js';
|
|
5
|
+
export async function writeRes(req, res, { type, data, charset, jsonBeautify, disableEtag }) {
|
|
6
|
+
const setContentType = (contentType) => {
|
|
7
|
+
if (res.headersSent || res.getHeader('content-type'))
|
|
8
|
+
return;
|
|
9
|
+
res.setHeader('content-type', `${contentType}${charset ? `; charset=${charset}` : ''}`);
|
|
10
|
+
};
|
|
11
|
+
let bufferOrStream;
|
|
12
|
+
switch (type) {
|
|
13
|
+
case 'text':
|
|
14
|
+
setContentType('text/plain');
|
|
15
|
+
case 'html':
|
|
16
|
+
setContentType('text/html');
|
|
17
|
+
// shared with text
|
|
18
|
+
bufferOrStream = Buffer.from(data, charset);
|
|
19
|
+
break;
|
|
20
|
+
case 'buffer':
|
|
21
|
+
setContentType('application/octet-stream');
|
|
22
|
+
bufferOrStream = data;
|
|
23
|
+
break;
|
|
24
|
+
case 'nodeStream':
|
|
25
|
+
setContentType('application/octet-stream');
|
|
26
|
+
bufferOrStream = data;
|
|
27
|
+
break;
|
|
28
|
+
case 'webStream':
|
|
29
|
+
setContentType('application/octet-stream');
|
|
30
|
+
bufferOrStream = Readable.fromWeb(data);
|
|
31
|
+
break;
|
|
32
|
+
case 'json':
|
|
33
|
+
setContentType('application/json');
|
|
34
|
+
bufferOrStream = Buffer.from(jsonBeautify ? JSON.stringify(data, null, 2) : JSON.stringify(data), charset);
|
|
35
|
+
break;
|
|
36
|
+
case 'redirect':
|
|
37
|
+
res.setHeader('location', data);
|
|
38
|
+
bufferOrStream = Buffer.from('', charset);
|
|
39
|
+
break;
|
|
40
|
+
case undefined:
|
|
41
|
+
// skip response. Some middleware may handle it outside the chain. For example, express middleware
|
|
42
|
+
return;
|
|
43
|
+
default:
|
|
44
|
+
if (!res.getHeader('content-type'))
|
|
45
|
+
res.setHeader('content-type', 'text/plain');
|
|
46
|
+
throw new Error(`unsupported response type ${type}`);
|
|
47
|
+
}
|
|
48
|
+
if (res.headersSent) {
|
|
49
|
+
if (res.writableFinished) {
|
|
50
|
+
// skipped: response is already finished
|
|
51
|
+
}
|
|
52
|
+
else if (res.writableEnded) {
|
|
53
|
+
const defer = makeDefer();
|
|
54
|
+
res.addListener('finish', defer.resolve);
|
|
55
|
+
await defer.promise;
|
|
56
|
+
// skipped: response is already ended
|
|
57
|
+
// chunk is not fully flushed yet
|
|
58
|
+
}
|
|
59
|
+
else
|
|
60
|
+
await promisify(res.end.bind(res))(undefined); // to be consistent, we end the response immediately
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// https://github.com/expressjs/express/blob/980d881e3b023db079de60477a2588a91f046ca5/lib/response.js#L210
|
|
64
|
+
if (res.statusCode === 204) { // No Content
|
|
65
|
+
res.removeHeader('content-type');
|
|
66
|
+
res.removeHeader('content-length');
|
|
67
|
+
res.removeHeader('transfer-encoding');
|
|
68
|
+
// write nothing
|
|
69
|
+
}
|
|
70
|
+
if (res.statusCode === 205) { // reset content. Tell client to clear the form, etc.
|
|
71
|
+
res.setHeader('content-length', 0);
|
|
72
|
+
res.removeHeader('transfer-encoding');
|
|
73
|
+
}
|
|
74
|
+
else if (req.method === 'HEAD') {
|
|
75
|
+
// write nothing
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
if (Buffer.isBuffer(bufferOrStream)) {
|
|
79
|
+
// support: 304 (etag), zipping, file etag and last modified
|
|
80
|
+
res.setHeader('content-length', bufferOrStream.length);
|
|
81
|
+
if (!disableEtag) {
|
|
82
|
+
const etag = entityTag(bufferOrStream);
|
|
83
|
+
const lastModified = res.getHeader('last-modified');
|
|
84
|
+
res.setHeader('ETag', etag);
|
|
85
|
+
if (isFreshETag(req, etag)) {
|
|
86
|
+
res.removeHeader('content-type');
|
|
87
|
+
res.removeHeader('content-length');
|
|
88
|
+
res.removeHeader('transfer-encoding');
|
|
89
|
+
res.statusCode = 304;
|
|
90
|
+
// write nothing
|
|
91
|
+
}
|
|
92
|
+
else
|
|
93
|
+
res.write(bufferOrStream);
|
|
94
|
+
}
|
|
95
|
+
else
|
|
96
|
+
res.write(bufferOrStream);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
bufferOrStream.pipe(res);
|
|
100
|
+
}
|
|
101
|
+
// we do not support content-encoding (gzip, deflate, br) and leave it to reverse proxy or CDN
|
|
102
|
+
}
|
|
103
|
+
await promisify(res.end.bind(res))(undefined); // some express middleware, such as express-session, requires explicitly passing chunk
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZHhIZWxwZXJzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2R4SGVscGVycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQUMsUUFBUSxFQUFDLE1BQU0sYUFBYSxDQUFBO0FBQ3BDLE9BQU8sU0FBUyxNQUFNLFFBQVEsQ0FBQTtBQUM5QixPQUFPLEVBQUMsU0FBUyxFQUFDLE1BQU0sV0FBVyxDQUFBO0FBQ25DLE9BQU8sRUFBQyxTQUFTLEVBQUUsV0FBVyxFQUFDLE1BQU0sV0FBVyxDQUFBO0FBb0NoRCxNQUFNLENBQUMsS0FBSyxVQUFVLFFBQVEsQ0FBQyxHQUFvQixFQUFFLEdBQW1CLEVBQUUsRUFBQyxJQUFJLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxZQUFZLEVBQUUsV0FBVyxFQUFZO0lBQ3BJLE1BQU0sY0FBYyxHQUFHLENBQUMsV0FBbUIsRUFBRSxFQUFFO1FBQzlDLElBQUksR0FBRyxDQUFDLFdBQVcsSUFBSSxHQUFHLENBQUMsU0FBUyxDQUFDLGNBQWMsQ0FBQztZQUFFLE9BQU07UUFDNUQsR0FBRyxDQUFDLFNBQVMsQ0FBQyxjQUFjLEVBQUUsR0FBRyxXQUFXLEdBQUcsT0FBTyxDQUFDLENBQUMsQ0FBQyxhQUFhLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFBO0lBQ3hGLENBQUMsQ0FBQTtJQUNELElBQUksY0FBYyxDQUFBO0lBRWxCLFFBQVEsSUFBSSxFQUFFLENBQUM7UUFDZCxLQUFLLE1BQU07WUFDVixjQUFjLENBQUMsWUFBWSxDQUFDLENBQUE7UUFDN0IsS0FBSyxNQUFNO1lBQ1YsY0FBYyxDQUFDLFdBQVcsQ0FBQyxDQUFBO1lBQzNCLG1CQUFtQjtZQUNuQixjQUFjLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsT0FBTyxDQUFDLENBQUE7WUFDM0MsTUFBSztRQUNOLEtBQUssUUFBUTtZQUNaLGNBQWMsQ0FBQywwQkFBMEIsQ0FBQyxD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
|