ultimate-express 1.2.16 → 1.2.17

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/src/request.js CHANGED
@@ -1,403 +1,403 @@
1
- /*
2
- Copyright 2024 dimden.dev
3
-
4
- Licensed under the Apache License, Version 2.0 (the "License");
5
- you may not use this file except in compliance with the License.
6
- You may obtain a copy of the License at
7
-
8
- http://www.apache.org/licenses/LICENSE-2.0
9
-
10
- Unless required by applicable law or agreed to in writing, software
11
- distributed under the License is distributed on an "AS IS" BASIS,
12
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- See the License for the specific language governing permissions and
14
- limitations under the License.
15
- */
16
-
17
- const { patternToRegex, deprecated } = require("./utils.js");
18
- const accepts = require("accepts");
19
- const typeis = require("type-is");
20
- const parseRange = require("range-parser");
21
- const proxyaddr = require("proxy-addr");
22
- const fresh = require("fresh");
23
- const { Readable } = require("stream");
24
-
25
- const discardedDuplicates = [
26
- "age", "authorization", "content-length", "content-type", "etag", "expires",
27
- "from", "host", "if-modified-since", "if-unmodified-since", "last-modified",
28
- "location", "max-forwards", "proxy-authorization", "referer", "retry-after",
29
- "server", "user-agent"
30
- ];
31
-
32
- let key = 0;
33
-
34
- module.exports = class Request extends Readable {
35
- #cachedQuery = null;
36
- #cachedHeaders = null;
37
- #cachedDistinctHeaders = null;
38
- #rawHeadersEntries = [];
39
- #cachedParsedIp = null;
40
- constructor(req, res, app) {
41
- super();
42
- this._res = res;
43
- this._req = req;
44
- this.readable = true;
45
- this._req.forEach((key, value) => {
46
- this.#rawHeadersEntries.push([key, value]);
47
- });
48
- this.key = key++;
49
- if(key > 100000) {
50
- key = 0;
51
- }
52
- this.app = app;
53
- this.urlQuery = req.getQuery() ?? '';
54
- if(this.urlQuery) {
55
- this.urlQuery = '?' + this.urlQuery;
56
- }
57
- this.originalUrl = req.getUrl() + this.urlQuery;
58
- this.url = this.originalUrl;
59
- const iq = this.url.indexOf('?');
60
- this.path = iq !== -1 ? this.url.substring(0, iq) : this.url;
61
- this.endsWithSlash = this.path[this.path.length - 1] === '/';
62
- this._opPath = this.path;
63
- if(this.endsWithSlash && this.path !== '/' && !this.app.get('strict routing')) {
64
- this._opPath = this._opPath.slice(0, -1);
65
- }
66
- this.method = req.getMethod().toUpperCase();
67
- this.params = {};
68
-
69
- this._gotParams = new Set();
70
- this._stack = [];
71
- this._paramStack = [];
72
- this.bufferedData = Buffer.allocUnsafe(0);
73
- this.receivedData = false;
74
- // reading ip is very slow in UWS, so its better to not do it unless truly needed
75
- if(this.app.needsIpAfterResponse || this.key < 100) {
76
- // if app needs ip after response, read it now because after response its not accessible
77
- // also read it for first 100 requests to not error
78
- this.rawIp = this._res.getRemoteAddress();
79
- }
80
-
81
- const additionalMethods = this.app.get('body methods');
82
- // skip reading body for non-POST requests
83
- // this makes it +10k req/sec faster
84
- if(
85
- this.method === 'POST' ||
86
- this.method === 'PUT' ||
87
- this.method === 'PATCH' ||
88
- (additionalMethods && additionalMethods.includes(this.method))
89
- ) {
90
- this._res.onData((ab, isLast) => {
91
- // make stream actually readable
92
- this.receivedData = true;
93
- // instead of pushing data immediately, buffer it
94
- // because writable streams cant handle the amount of data uWS gives (usually 512kb+)
95
- const chunk = Buffer.from(ab);
96
- this.bufferedData = Buffer.concat([this.bufferedData, chunk]);
97
- if(isLast) {
98
- // once its done start pushing data
99
- this._read();
100
- }
101
- });
102
- } else {
103
- this.receivedData = true;
104
- }
105
- }
106
-
107
- async _read() {
108
- if(!this.receivedData) {
109
- return;
110
- }
111
- if(this.bufferedData.length > 0) {
112
- // push 64kb chunks
113
- const chunk = this.bufferedData.subarray(0, 1024 * 64);
114
- this.bufferedData = this.bufferedData.subarray(1024 * 64);
115
- this.push(chunk);
116
- } else {
117
- this.push(null);
118
- }
119
- }
120
-
121
- get baseUrl() {
122
- let match = this.path.match(patternToRegex(this._stack.join(""), true));
123
- return match ? match[0] : '';
124
- }
125
-
126
- get #host() {
127
- const trust = this.app.get('trust proxy fn');
128
- if(!trust) {
129
- return this.get('host');
130
- }
131
- let val = this.headers['x-forwarded-host'];
132
- if (!val || !trust(this.connection.remoteAddress, 0)) {
133
- val = this.headers['host'];
134
- } else if (val.indexOf(',') !== -1) {
135
- // Note: X-Forwarded-Host is normally only ever a
136
- // single value, but this is to be safe.
137
- val = val.substring(0, val.indexOf(',')).trimRight()
138
- }
139
-
140
- return val ? val.split(':')[0] : undefined;
141
- }
142
-
143
- get host() {
144
- deprecated('req.host', 'req.hostname');
145
- return this.hostname;
146
- }
147
-
148
- get hostname() {
149
- const host = this.#host;
150
- if(!host) return this.headers['host'].split(':')[0];
151
- const offset = host[0] === '[' ? host.indexOf(']') + 1 : 0;
152
- const index = host.indexOf(':', offset);
153
- return index !== -1 ? host.slice(0, index) : host;
154
- }
155
-
156
- get httpVersion() {
157
- return '1.1';
158
- }
159
-
160
- get httpVersionMajor() {
161
- return 1;
162
- }
163
-
164
- get httpVersionMinor() {
165
- return 1;
166
- }
167
-
168
- get ip() {
169
- const trust = this.app.get('trust proxy fn');
170
- if(!trust) {
171
- return this.parsedIp;
172
- }
173
- return proxyaddr(this, trust);
174
- }
175
-
176
- get ips() {
177
- const trust = this.app.get('trust proxy fn');
178
- if(!trust) {
179
- return [];
180
- }
181
- const addrs = proxyaddr.all(this, trust);
182
- addrs.reverse().pop();
183
- return addrs;
184
- }
185
-
186
- get protocol() {
187
- const proto = this.app.ssl ? 'https' : 'http';
188
- const trust = this.app.get('trust proxy fn');
189
- if(!trust) {
190
- return proto;
191
- }
192
- if(!trust(this.connection.remoteAddress, 0)) {
193
- return proto;
194
- }
195
- const header = this.headers['x-forwarded-proto'] || proto;
196
- const index = header.indexOf(',');
197
-
198
- return index !== -1 ? header.slice(0, index).trim() : header.trim();
199
- }
200
-
201
- get query() {
202
- if(this.#cachedQuery) {
203
- return this.#cachedQuery;
204
- }
205
- const qp = this.app.get('query parser fn');
206
- if(qp) {
207
- this.#cachedQuery = qp(this.urlQuery.slice(1));
208
- } else {
209
- this.#cachedQuery = {};
210
- }
211
- return this.#cachedQuery;
212
- }
213
-
214
- get secure() {
215
- return this.protocol === 'https';
216
- }
217
-
218
- get subdomains() {
219
- let host = this.hostname;
220
- let subdomains = host.split('.');
221
- const so = this.app.get('subdomain offset');
222
- if(so === 0) {
223
- return subdomains.reverse();
224
- }
225
- return subdomains.slice(0, -so).reverse();
226
- }
227
-
228
- get xhr() {
229
- return this.headers['x-requested-with'] === 'XMLHttpRequest';
230
- }
231
-
232
- get parsedIp() {
233
- if(this.#cachedParsedIp) {
234
- return this.#cachedParsedIp;
235
- }
236
- const finished = !this.res.socket.writable;
237
- if(finished) {
238
- // mark app as one that needs ip after response
239
- this.app.needsIpAfterResponse = true;
240
- }
241
- if(!this.rawIp) {
242
- if(finished) {
243
- // fallback once
244
- return '127.0.0.1';
245
- }
246
- this.rawIp = this._res.getRemoteAddress();
247
- }
248
- let ip = '';
249
- if(this.rawIp.byteLength === 4) {
250
- // ipv4
251
- ip = this.rawIp.join('.');
252
- } else {
253
- // ipv6
254
- const dv = new DataView(this.rawIp);
255
- for(let i = 0; i < 8; i++) {
256
- ip += dv.getUint16(i * 2).toString(16).padStart(4, '0');
257
- if(i < 7) {
258
- ip += ':';
259
- }
260
- }
261
- }
262
- this.#cachedParsedIp = ip;
263
- return ip;
264
- }
265
-
266
- get connection() {
267
- return {
268
- remoteAddress: this.parsedIp,
269
- localPort: this.app.port,
270
- remotePort: this.app.port,
271
- encrypted: this.app.ssl,
272
- };
273
- }
274
-
275
- get socket() {
276
- return this.connection;
277
- }
278
-
279
- get fresh() {
280
- if(this.method !== 'HEAD' && this.method !== 'GET') {
281
- return false;
282
- }
283
- if((this.res.statusCode >= 200 && this.res.statusCode < 300) || this.res.statusCode === 304) {
284
- return fresh(this.headers, {
285
- 'etag': this.res.headers['etag'],
286
- 'last-modified': this.res.headers['last-modified'],
287
- });
288
- }
289
- return false;
290
- }
291
-
292
- get stale() {
293
- return !this.fresh;
294
- }
295
-
296
- get(field) {
297
- field = field.toLowerCase();
298
- if(field === 'referrer' || field === 'referer') {
299
- const res = this.headers['referrer'];
300
- if(!res) {
301
- return this.headers['referer'];
302
- }
303
- return res;
304
- }
305
- return this.headers[field];
306
- }
307
-
308
- accepts(...types) {
309
- const accept = accepts({ headers: this.headers });
310
- return accept.types(...types);
311
- }
312
-
313
- acceptsCharsets(...charsets) {
314
- const accept = accepts({ headers: this.headers });
315
- return accept.charsets(...charsets);
316
- }
317
-
318
- acceptsEncodings(...encodings) {
319
- const accept = accepts({ headers: this.headers });
320
- return accept.encodings(...encodings);
321
- }
322
-
323
- acceptsLanguages(...languages) {
324
- const accept = accepts({ headers: this.headers });
325
- return accept.languages(...languages);
326
- }
327
-
328
- is(type) {
329
- return typeis(this, type);
330
- }
331
-
332
- param(name, defaultValue) {
333
- deprecated('req.param(name)', 'req.params, req.body, or req.query');
334
- if(this.params[name]) {
335
- return this.params[name];
336
- }
337
- if(this.body && this.body[name]) {
338
- return this.body[name];
339
- }
340
- return this.query[name] ?? defaultValue;
341
- }
342
-
343
- range(size, options) {
344
- const range = this.headers['range'];
345
- if(!range) return;
346
- return parseRange(size, range, options);
347
- }
348
-
349
- get headers() {
350
- // https://nodejs.org/api/http.html#messageheaders
351
- if(this.#cachedHeaders) {
352
- return this.#cachedHeaders;
353
- }
354
- let headers = {};
355
- this.#rawHeadersEntries.forEach((val) => {
356
- const key = val[0].toLowerCase();
357
- const value = val[1];
358
- if(headers[key]) {
359
- if(discardedDuplicates.includes(key)) {
360
- return;
361
- }
362
- if(key === 'cookie') {
363
- headers[key] += '; ' + value;
364
- } else if(key === 'set-cookie') {
365
- headers[key].push(value);
366
- } else {
367
- headers[key] += ', ' + value;
368
- }
369
- return;
370
- }
371
- if(key === 'set-cookie') {
372
- headers[key] = [value];
373
- } else {
374
- headers[key] = value;
375
- }
376
- });
377
- this.#cachedHeaders = headers;
378
- return headers;
379
- }
380
-
381
- get headersDistinct() {
382
- if(this.#cachedDistinctHeaders) {
383
- return this.#cachedDistinctHeaders;
384
- }
385
- let headers = {};
386
- this.#rawHeadersEntries.forEach((val) => {
387
- if(!headers[val[0]]) {
388
- headers[val[0]] = [];
389
- }
390
- headers[val[0]].push(val[1]);
391
- });
392
- this.#cachedDistinctHeaders = headers;
393
- return headers;
394
- }
395
-
396
- get rawHeaders() {
397
- const res = [];
398
- this.#rawHeadersEntries.forEach((val) => {
399
- res.push(val[0], val[1]);
400
- });
401
- return res;
402
- }
1
+ /*
2
+ Copyright 2024 dimden.dev
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ const { patternToRegex, deprecated } = require("./utils.js");
18
+ const accepts = require("accepts");
19
+ const typeis = require("type-is");
20
+ const parseRange = require("range-parser");
21
+ const proxyaddr = require("proxy-addr");
22
+ const fresh = require("fresh");
23
+ const { Readable } = require("stream");
24
+
25
+ const discardedDuplicates = [
26
+ "age", "authorization", "content-length", "content-type", "etag", "expires",
27
+ "from", "host", "if-modified-since", "if-unmodified-since", "last-modified",
28
+ "location", "max-forwards", "proxy-authorization", "referer", "retry-after",
29
+ "server", "user-agent"
30
+ ];
31
+
32
+ let key = 0;
33
+
34
+ module.exports = class Request extends Readable {
35
+ #cachedQuery = null;
36
+ #cachedHeaders = null;
37
+ #cachedDistinctHeaders = null;
38
+ #rawHeadersEntries = [];
39
+ #cachedParsedIp = null;
40
+ constructor(req, res, app) {
41
+ super();
42
+ this._res = res;
43
+ this._req = req;
44
+ this.readable = true;
45
+ this._req.forEach((key, value) => {
46
+ this.#rawHeadersEntries.push([key, value]);
47
+ });
48
+ this.key = key++;
49
+ if(key > 100000) {
50
+ key = 0;
51
+ }
52
+ this.app = app;
53
+ this.urlQuery = req.getQuery() ?? '';
54
+ if(this.urlQuery) {
55
+ this.urlQuery = '?' + this.urlQuery;
56
+ }
57
+ this.originalUrl = req.getUrl() + this.urlQuery;
58
+ this.url = this.originalUrl;
59
+ const iq = this.url.indexOf('?');
60
+ this.path = iq !== -1 ? this.url.substring(0, iq) : this.url;
61
+ this.endsWithSlash = this.path[this.path.length - 1] === '/';
62
+ this._opPath = this.path;
63
+ if(this.endsWithSlash && this.path !== '/' && !this.app.get('strict routing')) {
64
+ this._opPath = this._opPath.slice(0, -1);
65
+ }
66
+ this.method = req.getCaseSensitiveMethod().toUpperCase();
67
+ this.params = {};
68
+
69
+ this._gotParams = new Set();
70
+ this._stack = [];
71
+ this._paramStack = [];
72
+ this.receivedData = false;
73
+ // reading ip is very slow in UWS, so its better to not do it unless truly needed
74
+ if(this.app.needsIpAfterResponse || this.key < 100) {
75
+ // if app needs ip after response, read it now because after response its not accessible
76
+ // also read it for first 100 requests to not error
77
+ this.rawIp = this._res.getRemoteAddress();
78
+ }
79
+
80
+ const additionalMethods = this.app.get('body methods');
81
+ // skip reading body for non-POST requests
82
+ // this makes it +10k req/sec faster
83
+ if(
84
+ this.method === 'POST' ||
85
+ this.method === 'PUT' ||
86
+ this.method === 'PATCH' ||
87
+ (additionalMethods && additionalMethods.includes(this.method))
88
+ ) {
89
+ this.bufferedData = Buffer.allocUnsafe(0);
90
+ this._res.onData((ab, isLast) => {
91
+ // make stream actually readable
92
+ this.receivedData = true;
93
+ // instead of pushing data immediately, buffer it
94
+ // because writable streams cant handle the amount of data uWS gives (usually 512kb+)
95
+ const chunk = Buffer.from(ab);
96
+ this.bufferedData = Buffer.concat([this.bufferedData, chunk]);
97
+ if(isLast) {
98
+ // once its done start pushing data
99
+ this._read();
100
+ }
101
+ });
102
+ } else {
103
+ this.receivedData = true;
104
+ }
105
+ }
106
+
107
+ async _read() {
108
+ if(!this.receivedData || !this.bufferedData) {
109
+ return;
110
+ }
111
+ if(this.bufferedData.length > 0) {
112
+ // push 64kb chunks
113
+ const chunk = this.bufferedData.subarray(0, 1024 * 64);
114
+ this.bufferedData = this.bufferedData.subarray(1024 * 64);
115
+ this.push(chunk);
116
+ } else {
117
+ this.push(null);
118
+ }
119
+ }
120
+
121
+ get baseUrl() {
122
+ let match = this.path.match(patternToRegex(this._stack.join(""), true));
123
+ return match ? match[0] : '';
124
+ }
125
+
126
+ get #host() {
127
+ const trust = this.app.get('trust proxy fn');
128
+ if(!trust) {
129
+ return this.get('host');
130
+ }
131
+ let val = this.headers['x-forwarded-host'];
132
+ if (!val || !trust(this.connection.remoteAddress, 0)) {
133
+ val = this.headers['host'];
134
+ } else if (val.indexOf(',') !== -1) {
135
+ // Note: X-Forwarded-Host is normally only ever a
136
+ // single value, but this is to be safe.
137
+ val = val.substring(0, val.indexOf(',')).trimRight()
138
+ }
139
+
140
+ return val ? val.split(':')[0] : undefined;
141
+ }
142
+
143
+ get host() {
144
+ deprecated('req.host', 'req.hostname');
145
+ return this.hostname;
146
+ }
147
+
148
+ get hostname() {
149
+ const host = this.#host;
150
+ if(!host) return this.headers['host'].split(':')[0];
151
+ const offset = host[0] === '[' ? host.indexOf(']') + 1 : 0;
152
+ const index = host.indexOf(':', offset);
153
+ return index !== -1 ? host.slice(0, index) : host;
154
+ }
155
+
156
+ get httpVersion() {
157
+ return '1.1';
158
+ }
159
+
160
+ get httpVersionMajor() {
161
+ return 1;
162
+ }
163
+
164
+ get httpVersionMinor() {
165
+ return 1;
166
+ }
167
+
168
+ get ip() {
169
+ const trust = this.app.get('trust proxy fn');
170
+ if(!trust) {
171
+ return this.parsedIp;
172
+ }
173
+ return proxyaddr(this, trust);
174
+ }
175
+
176
+ get ips() {
177
+ const trust = this.app.get('trust proxy fn');
178
+ if(!trust) {
179
+ return [];
180
+ }
181
+ const addrs = proxyaddr.all(this, trust);
182
+ addrs.reverse().pop();
183
+ return addrs;
184
+ }
185
+
186
+ get protocol() {
187
+ const proto = this.app.ssl ? 'https' : 'http';
188
+ const trust = this.app.get('trust proxy fn');
189
+ if(!trust) {
190
+ return proto;
191
+ }
192
+ if(!trust(this.connection.remoteAddress, 0)) {
193
+ return proto;
194
+ }
195
+ const header = this.headers['x-forwarded-proto'] || proto;
196
+ const index = header.indexOf(',');
197
+
198
+ return index !== -1 ? header.slice(0, index).trim() : header.trim();
199
+ }
200
+
201
+ get query() {
202
+ if(this.#cachedQuery) {
203
+ return this.#cachedQuery;
204
+ }
205
+ const qp = this.app.get('query parser fn');
206
+ if(qp) {
207
+ this.#cachedQuery = qp(this.urlQuery.slice(1));
208
+ } else {
209
+ this.#cachedQuery = {};
210
+ }
211
+ return this.#cachedQuery;
212
+ }
213
+
214
+ get secure() {
215
+ return this.protocol === 'https';
216
+ }
217
+
218
+ get subdomains() {
219
+ let host = this.hostname;
220
+ let subdomains = host.split('.');
221
+ const so = this.app.get('subdomain offset');
222
+ if(so === 0) {
223
+ return subdomains.reverse();
224
+ }
225
+ return subdomains.slice(0, -so).reverse();
226
+ }
227
+
228
+ get xhr() {
229
+ return this.headers['x-requested-with'] === 'XMLHttpRequest';
230
+ }
231
+
232
+ get parsedIp() {
233
+ if(this.#cachedParsedIp) {
234
+ return this.#cachedParsedIp;
235
+ }
236
+ const finished = !this.res.socket.writable;
237
+ if(finished) {
238
+ // mark app as one that needs ip after response
239
+ this.app.needsIpAfterResponse = true;
240
+ }
241
+ if(!this.rawIp) {
242
+ if(finished) {
243
+ // fallback once
244
+ return '127.0.0.1';
245
+ }
246
+ this.rawIp = this._res.getRemoteAddress();
247
+ }
248
+ let ip = '';
249
+ if(this.rawIp.byteLength === 4) {
250
+ // ipv4
251
+ ip = this.rawIp.join('.');
252
+ } else {
253
+ // ipv6
254
+ const dv = new DataView(this.rawIp);
255
+ for(let i = 0; i < 8; i++) {
256
+ ip += dv.getUint16(i * 2).toString(16).padStart(4, '0');
257
+ if(i < 7) {
258
+ ip += ':';
259
+ }
260
+ }
261
+ }
262
+ this.#cachedParsedIp = ip;
263
+ return ip;
264
+ }
265
+
266
+ get connection() {
267
+ return {
268
+ remoteAddress: this.parsedIp,
269
+ localPort: this.app.port,
270
+ remotePort: this.app.port,
271
+ encrypted: this.app.ssl,
272
+ };
273
+ }
274
+
275
+ get socket() {
276
+ return this.connection;
277
+ }
278
+
279
+ get fresh() {
280
+ if(this.method !== 'HEAD' && this.method !== 'GET') {
281
+ return false;
282
+ }
283
+ if((this.res.statusCode >= 200 && this.res.statusCode < 300) || this.res.statusCode === 304) {
284
+ return fresh(this.headers, {
285
+ 'etag': this.res.headers['etag'],
286
+ 'last-modified': this.res.headers['last-modified'],
287
+ });
288
+ }
289
+ return false;
290
+ }
291
+
292
+ get stale() {
293
+ return !this.fresh;
294
+ }
295
+
296
+ get(field) {
297
+ field = field.toLowerCase();
298
+ if(field === 'referrer' || field === 'referer') {
299
+ const res = this.headers['referrer'];
300
+ if(!res) {
301
+ return this.headers['referer'];
302
+ }
303
+ return res;
304
+ }
305
+ return this.headers[field];
306
+ }
307
+
308
+ accepts(...types) {
309
+ const accept = accepts({ headers: this.headers });
310
+ return accept.types(...types);
311
+ }
312
+
313
+ acceptsCharsets(...charsets) {
314
+ const accept = accepts({ headers: this.headers });
315
+ return accept.charsets(...charsets);
316
+ }
317
+
318
+ acceptsEncodings(...encodings) {
319
+ const accept = accepts({ headers: this.headers });
320
+ return accept.encodings(...encodings);
321
+ }
322
+
323
+ acceptsLanguages(...languages) {
324
+ const accept = accepts({ headers: this.headers });
325
+ return accept.languages(...languages);
326
+ }
327
+
328
+ is(type) {
329
+ return typeis(this, type);
330
+ }
331
+
332
+ param(name, defaultValue) {
333
+ deprecated('req.param(name)', 'req.params, req.body, or req.query');
334
+ if(this.params[name]) {
335
+ return this.params[name];
336
+ }
337
+ if(this.body && this.body[name]) {
338
+ return this.body[name];
339
+ }
340
+ return this.query[name] ?? defaultValue;
341
+ }
342
+
343
+ range(size, options) {
344
+ const range = this.headers['range'];
345
+ if(!range) return;
346
+ return parseRange(size, range, options);
347
+ }
348
+
349
+ get headers() {
350
+ // https://nodejs.org/api/http.html#messageheaders
351
+ if(this.#cachedHeaders) {
352
+ return this.#cachedHeaders;
353
+ }
354
+ let headers = {};
355
+ this.#rawHeadersEntries.forEach((val) => {
356
+ const key = val[0].toLowerCase();
357
+ const value = val[1];
358
+ if(headers[key]) {
359
+ if(discardedDuplicates.includes(key)) {
360
+ return;
361
+ }
362
+ if(key === 'cookie') {
363
+ headers[key] += '; ' + value;
364
+ } else if(key === 'set-cookie') {
365
+ headers[key].push(value);
366
+ } else {
367
+ headers[key] += ', ' + value;
368
+ }
369
+ return;
370
+ }
371
+ if(key === 'set-cookie') {
372
+ headers[key] = [value];
373
+ } else {
374
+ headers[key] = value;
375
+ }
376
+ });
377
+ this.#cachedHeaders = headers;
378
+ return headers;
379
+ }
380
+
381
+ get headersDistinct() {
382
+ if(this.#cachedDistinctHeaders) {
383
+ return this.#cachedDistinctHeaders;
384
+ }
385
+ let headers = {};
386
+ this.#rawHeadersEntries.forEach((val) => {
387
+ if(!headers[val[0]]) {
388
+ headers[val[0]] = [];
389
+ }
390
+ headers[val[0]].push(val[1]);
391
+ });
392
+ this.#cachedDistinctHeaders = headers;
393
+ return headers;
394
+ }
395
+
396
+ get rawHeaders() {
397
+ const res = [];
398
+ this.#rawHeadersEntries.forEach((val) => {
399
+ res.push(val[0], val[1]);
400
+ });
401
+ return res;
402
+ }
403
403
  }