@dooboostore/simple-boot-http-server 1.0.33 → 1.0.35
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 +34 -0
- package/dist/cjs/SimpleBootHttpServer.js +29 -6
- package/dist/cjs/SimpleBootHttpServer.js.map +2 -2
- package/dist/cjs/models/RequestResponse.js +38 -23
- package/dist/cjs/models/RequestResponse.js.map +2 -2
- package/dist/esm/SimpleBootHttpServer.js +29 -6
- package/dist/esm/SimpleBootHttpServer.js.map +2 -2
- package/dist/esm/models/RequestResponse.js +38 -23
- package/dist/esm/models/RequestResponse.js.map +2 -2
- package/dist/esm-bundle/dooboostore-simple-boot-http-server.esm.js +68 -30
- package/dist/esm-bundle/dooboostore-simple-boot-http-server.esm.js.map +2 -2
- package/dist/types/SimpleBootHttpServer.d.ts +8 -0
- package/dist/types/SimpleBootHttpServer.d.ts.map +1 -1
- package/dist/types/models/RequestResponse.d.ts.map +1 -1
- package/dist/umd-bundle/dooboostore-simple-boot-http-server.umd.js +68 -30
- package/dist/umd-bundle/dooboostore-simple-boot-http-server.umd.js.map +2 -2
- package/package.json +11 -9
- package/src/SimpleBootHttpServer.ts +36 -6
- package/src/models/RequestResponse.ts +58 -24
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dooboostore/simple-boot-http-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.35",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -55,16 +55,18 @@
|
|
|
55
55
|
"SeoDH <adonis_84@naver.com> (https://github.com/SeoDH)",
|
|
56
56
|
"tkyung (https://github.com/tkyungDev)",
|
|
57
57
|
"Hoil-Gang (https://github.com/khl1154)",
|
|
58
|
-
"hwangsero (https://github.com/hwangsero)"
|
|
58
|
+
"hwangsero (https://github.com/hwangsero)",
|
|
59
|
+
"visualkhh <visualkhh@gmail.com> (https://github.com/visualkhh)"
|
|
59
60
|
],
|
|
60
|
-
"homepage": "https://github.
|
|
61
|
+
"homepage": "https://dooboostore-develop.github.io/package/simple-boot-http-server",
|
|
61
62
|
"bugs": {
|
|
62
|
-
"url": "https://github.com/dooboostore-develop/packages",
|
|
63
|
+
"url": "https://github.com/dooboostore-develop/packages/issues",
|
|
63
64
|
"email": "dooboostore@gmail.com"
|
|
64
65
|
},
|
|
65
66
|
"repository": {
|
|
66
67
|
"type": "git",
|
|
67
|
-
"url": "https://github.com/dooboostore-develop/packages"
|
|
68
|
+
"url": "git+https://github.com/dooboostore-develop/packages.git",
|
|
69
|
+
"directory": "packages/@dooboostore/simple-boot-http-server"
|
|
68
70
|
},
|
|
69
71
|
"peerDependencies": {
|
|
70
72
|
"reflect-metadata": "^0.2.2"
|
|
@@ -98,11 +100,11 @@
|
|
|
98
100
|
"mime-types": "^2.1.34",
|
|
99
101
|
"node-gzip": "^1.1.2",
|
|
100
102
|
"ws": "^8.18.3",
|
|
101
|
-
"@dooboostore/core": "1.0.33",
|
|
102
|
-
"@dooboostore/dom-render": "1.0.115",
|
|
103
103
|
"@dooboostore/core-node": "1.0.14",
|
|
104
|
-
"@dooboostore/
|
|
105
|
-
"@dooboostore/
|
|
104
|
+
"@dooboostore/dom-render": "1.0.115",
|
|
105
|
+
"@dooboostore/core": "1.0.33",
|
|
106
|
+
"@dooboostore/simple-boot": "1.0.60",
|
|
107
|
+
"@dooboostore/core-web": "1.0.14"
|
|
106
108
|
},
|
|
107
109
|
"scripts": {
|
|
108
110
|
"clean": "rm -rf dist",
|
|
@@ -44,6 +44,27 @@ export class SimpleBootHttpServer extends SimpleApplication {
|
|
|
44
44
|
this.sessionManager = new SessionManager(this.option);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Final response flush that can never escape as an unhandled rejection.
|
|
49
|
+
* When the response is already gone (sent, closed, or destroyed mid-write),
|
|
50
|
+
* there is nobody left to send to, so gone-response write errors
|
|
51
|
+
* (ERR_HTTP_HEADERS_SENT, ERR_STREAM_ALREADY_FINISHED,
|
|
52
|
+
* ERR_STREAM_DESTROYED, ECONNRESET) are swallowed; anything else still throws.
|
|
53
|
+
*/
|
|
54
|
+
private async safeResEnd(rr: RequestResponse): Promise<void> {
|
|
55
|
+
if (rr.resIsDone()) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
await rr.resEnd();
|
|
60
|
+
} catch (e: any) {
|
|
61
|
+
const code = e && (e.code || '');
|
|
62
|
+
if (code !== 'ERR_HTTP_HEADERS_SENT' && code !== 'ERR_STREAM_ALREADY_FINISHED' && code !== 'ERR_STREAM_DESTROYED' && code !== 'ECONNRESET') {
|
|
63
|
+
throw e;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
47
68
|
|
|
48
69
|
public run(otherInstanceSim?: Map<ConstructorType<any> | Function | SimConfig | symbol, any>) {
|
|
49
70
|
otherInstanceSim ??= new Map<ConstructorType<any> | Function | SimConfig | symbol, any>();
|
|
@@ -95,7 +116,7 @@ export class SimpleBootHttpServer extends SimpleApplication {
|
|
|
95
116
|
}
|
|
96
117
|
|
|
97
118
|
if (!rr.resIsDone()) {
|
|
98
|
-
|
|
119
|
+
await this.safeResEnd(rr);
|
|
99
120
|
}
|
|
100
121
|
});
|
|
101
122
|
res.on('error', async () => {
|
|
@@ -113,7 +134,7 @@ export class SimpleBootHttpServer extends SimpleApplication {
|
|
|
113
134
|
}
|
|
114
135
|
|
|
115
136
|
if (!rr.resIsDone()) {
|
|
116
|
-
|
|
137
|
+
await this.safeResEnd(rr);
|
|
117
138
|
}
|
|
118
139
|
});
|
|
119
140
|
|
|
@@ -351,14 +372,18 @@ export class SimpleBootHttpServer extends SimpleApplication {
|
|
|
351
372
|
headers[HttpHeaders.ContentType] = contentType;
|
|
352
373
|
}
|
|
353
374
|
rr.resSetHeaders(headers);
|
|
354
|
-
|
|
375
|
+
if (resConfig?.status !== undefined) {
|
|
376
|
+
// Explicit decorator status wins. The Node default (200) is left
|
|
377
|
+
// alone otherwise so a handler-set status (e.g. via resEnd(Response))
|
|
378
|
+
// is not clobbered back to 200.
|
|
379
|
+
rr.resSetStatusCode(resConfig.status);
|
|
380
|
+
}
|
|
355
381
|
await rr.resStream(data, { compress: stream?.compress });
|
|
356
382
|
} else {
|
|
357
383
|
if (it.config?.resolver) {
|
|
358
384
|
const execute = typeof it.config.resolver === 'function' ? this.simstanceManager.getOrNewSim({target: it.config.resolver}) : it.config.resolver;
|
|
359
385
|
data = await execute?.resolve?.(data, rr, resConfig);
|
|
360
386
|
}
|
|
361
|
-
const status = resConfig?.status ?? HttpStatus.Ok;
|
|
362
387
|
const headers = resConfig?.header ?? {};
|
|
363
388
|
if (resConfig?.contentType) {
|
|
364
389
|
headers[HttpHeaders.ContentType] = resConfig.contentType;
|
|
@@ -369,7 +394,12 @@ export class SimpleBootHttpServer extends SimpleApplication {
|
|
|
369
394
|
data = JSON.stringify(data);
|
|
370
395
|
}
|
|
371
396
|
rr.resSetHeaders(headers)
|
|
372
|
-
|
|
397
|
+
if (resConfig?.status !== undefined) {
|
|
398
|
+
// An explicit decorator status wins. Otherwise leave the status
|
|
399
|
+
// alone (Node defaults to 200) so a handler-set status is not
|
|
400
|
+
// clobbered back to 200.
|
|
401
|
+
rr.resSetStatusCode(resConfig.status);
|
|
402
|
+
}
|
|
373
403
|
rr.resWrite(data);
|
|
374
404
|
}
|
|
375
405
|
}
|
|
@@ -419,7 +449,7 @@ export class SimpleBootHttpServer extends SimpleApplication {
|
|
|
419
449
|
}
|
|
420
450
|
|
|
421
451
|
if (!rr.resIsDone()) {
|
|
422
|
-
|
|
452
|
+
await this.safeResEnd(rr);
|
|
423
453
|
}
|
|
424
454
|
});
|
|
425
455
|
|
|
@@ -18,6 +18,14 @@ import { Blob } from 'node:buffer';
|
|
|
18
18
|
import * as fs from 'fs';
|
|
19
19
|
import * as os from 'os';
|
|
20
20
|
import { RandomUtils } from '@dooboostore/core';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Responses with a send already in flight. Keyed by the underlying
|
|
24
|
+
* ServerResponse (shared by RequestResponse and its chains), so concurrent
|
|
25
|
+
* resEnd() calls for one response serialize instead of racing each other
|
|
26
|
+
* through gzip/setHeader into ERR_HTTP_HEADERS_SENT.
|
|
27
|
+
*/
|
|
28
|
+
const resEndingSet = new WeakSet<object>();
|
|
21
29
|
import { FileUtils } from '@dooboostore/core-node';
|
|
22
30
|
import { ConvertUtils as CoreConvertUtils } from '@dooboostore/core';
|
|
23
31
|
import { ConvertUtils } from '@dooboostore/core-node';
|
|
@@ -491,6 +499,12 @@ export class RequestResponse {
|
|
|
491
499
|
}
|
|
492
500
|
|
|
493
501
|
resWrite(chunk: string | Buffer | any, encoding: BufferEncoding = 'utf8') {
|
|
502
|
+
if (chunk === undefined) {
|
|
503
|
+
// A handler that already wrote via rr.resEnd()/resWrite() and returns
|
|
504
|
+
// undefined must not have its buffered body clobbered by the pipeline's
|
|
505
|
+
// return-value write (express#7510-class probe caught this).
|
|
506
|
+
return this.createRequestResponseChain(this.resWriteChunk);
|
|
507
|
+
}
|
|
494
508
|
this.resWriteChunk = chunk;
|
|
495
509
|
return this.createRequestResponseChain(this.resWriteChunk);
|
|
496
510
|
}
|
|
@@ -532,32 +546,52 @@ export class RequestResponse {
|
|
|
532
546
|
}
|
|
533
547
|
|
|
534
548
|
async resEnd(chunk?: string | Buffer | Blob | ArrayBuffer | Response) {
|
|
535
|
-
if (
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
this.res.statusCode = chunk.status;
|
|
544
|
-
this.resWriteChunk = Buffer.from(await chunk.arrayBuffer());
|
|
545
|
-
} else if (chunk instanceof Blob) {
|
|
546
|
-
this.resWriteChunk = Buffer.from(await chunk.arrayBuffer());
|
|
547
|
-
if (!this.resHeader(HttpHeaders.ContentType)) {
|
|
548
|
-
this.res.setHeader(HttpHeaders.ContentType, chunk.type);
|
|
549
|
-
}
|
|
550
|
-
} else if (chunk instanceof ArrayBuffer) {
|
|
551
|
-
this.resWriteChunk = Buffer.from(chunk);
|
|
552
|
-
} else {
|
|
553
|
-
this.resWriteChunk = chunk ?? this.resWriteChunk;
|
|
549
|
+
if (resEndingSet.has(this.res) || this.resIsDone()) {
|
|
550
|
+
// A send is already in flight (or finished): concurrent resEnd() calls
|
|
551
|
+
// — e.g. a handler-sent body followed by the pipeline's final flush —
|
|
552
|
+
// must not start a second send. Without this guard the two sends race
|
|
553
|
+
// through gzip/setHeader and the loser throws ERR_HTTP_HEADERS_SENT.
|
|
554
|
+
// The set is keyed by the underlying response (shared by chains).
|
|
555
|
+
return;
|
|
554
556
|
}
|
|
557
|
+
resEndingSet.add(this.res);
|
|
558
|
+
try {
|
|
559
|
+
if (typeof ArrayBuffer !== 'undefined' && ArrayBuffer.isView(chunk) && !Buffer.isBuffer(chunk)) {
|
|
560
|
+
// Typed-array views (incl. DataView, which Node's res.end() rejects outright)
|
|
561
|
+
// are normalized to their exact byte range. Native res.end() already honors
|
|
562
|
+
// byteOffset for typed arrays, but DataView throws ERR_INVALID_ARG_TYPE,
|
|
563
|
+
// so every non-Buffer view goes through here (expressjs/express#7510 class).
|
|
564
|
+
const view = chunk as unknown as { buffer: ArrayBuffer; byteOffset: number; byteLength: number };
|
|
565
|
+
this.resWriteChunk = Buffer.from(view.buffer, view.byteOffset, view.byteLength);
|
|
566
|
+
} else if (chunk instanceof Response) {
|
|
567
|
+
chunk.headers.forEach((value, key) => {
|
|
568
|
+
this.res.setHeader(`ORIGIN-${key}`, value);
|
|
569
|
+
});
|
|
570
|
+
if (!this.resHeader(HttpHeaders.ContentType)) {
|
|
571
|
+
const contentType = chunk.headers.get(HttpHeaders.ContentType);
|
|
572
|
+
if (contentType) this.res.setHeader(HttpHeaders.ContentType, contentType);
|
|
573
|
+
}
|
|
574
|
+
this.res.statusCode = chunk.status;
|
|
575
|
+
this.resWriteChunk = Buffer.from(await chunk.arrayBuffer());
|
|
576
|
+
} else if (chunk instanceof Blob) {
|
|
577
|
+
this.resWriteChunk = Buffer.from(await chunk.arrayBuffer());
|
|
578
|
+
if (!this.resHeader(HttpHeaders.ContentType)) {
|
|
579
|
+
this.res.setHeader(HttpHeaders.ContentType, chunk.type);
|
|
580
|
+
}
|
|
581
|
+
} else if (chunk instanceof ArrayBuffer) {
|
|
582
|
+
this.resWriteChunk = Buffer.from(chunk);
|
|
583
|
+
} else {
|
|
584
|
+
this.resWriteChunk = chunk ?? this.resWriteChunk;
|
|
585
|
+
}
|
|
555
586
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
587
|
+
if (this.req.readable) {
|
|
588
|
+
await this.reqBodyData();
|
|
589
|
+
await this.resEndChunk();
|
|
590
|
+
} else {
|
|
591
|
+
await this.resEndChunk();
|
|
592
|
+
}
|
|
593
|
+
} finally {
|
|
594
|
+
resEndingSet.delete(this.res);
|
|
561
595
|
}
|
|
562
596
|
}
|
|
563
597
|
|