@linebridge/engine-neo 1.2.2 → 1.3.0
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/lib/response.d.ts +12 -3
- package/lib/response.js +153 -130
- package/package.json +1 -1
package/lib/response.d.ts
CHANGED
|
@@ -43,6 +43,10 @@ export default class Response<TServer = Server<any>> implements BaseHttpResponse
|
|
|
43
43
|
_status_code: number;
|
|
44
44
|
/** Custom status message (overrides the default from STATUS_CODES). */
|
|
45
45
|
_status_message: string | null;
|
|
46
|
+
/** Cached engine reference to avoid walking route.server on every response. */
|
|
47
|
+
_engine: EngineAdaptor | null;
|
|
48
|
+
/** Whether at least one header was queued. Avoids enumerating an empty object. */
|
|
49
|
+
_has_headers: boolean;
|
|
46
50
|
/** Response headers (key → value or key → string[]). */
|
|
47
51
|
_headers: Record<string, string | string[]>;
|
|
48
52
|
/** Set-Cookie headers (name → serialized cookie string). */
|
|
@@ -141,11 +145,16 @@ export default class Response<TServer = Server<any>> implements BaseHttpResponse
|
|
|
141
145
|
* Must return `true` if the chunk was successfully written.
|
|
142
146
|
*/
|
|
143
147
|
drain(handler: (offset: number) => boolean): void;
|
|
148
|
+
/**
|
|
149
|
+
* Marks the response as completed and fires the lifecycle listeners.
|
|
150
|
+
* Used by every path that finishes a response, including streaming.
|
|
151
|
+
*/
|
|
152
|
+
_mark_completed(): void;
|
|
144
153
|
/**
|
|
145
154
|
* Sends the response body and marks the response as completed.
|
|
146
155
|
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
156
|
+
* All uWS writes are wrapped in a single cork callback so status,
|
|
157
|
+
* headers and body are flushed atomically.
|
|
149
158
|
*
|
|
150
159
|
* Fires `finish` and `close` event listeners.
|
|
151
160
|
*/
|
|
@@ -178,7 +187,7 @@ export default class Response<TServer = Server<any>> implements BaseHttpResponse
|
|
|
178
187
|
redirect(url: string): boolean;
|
|
179
188
|
/**
|
|
180
189
|
* Fast-path send used by the `_invokeHandler`.
|
|
181
|
-
*
|
|
190
|
+
* Corks the writes so status, headers and body are flushed atomically.
|
|
182
191
|
*/
|
|
183
192
|
_sendFast(body: any): void;
|
|
184
193
|
/**
|
package/lib/response.js
CHANGED
|
@@ -46,6 +46,10 @@ export default class Response {
|
|
|
46
46
|
_status_code;
|
|
47
47
|
/** Custom status message (overrides the default from STATUS_CODES). */
|
|
48
48
|
_status_message;
|
|
49
|
+
/** Cached engine reference to avoid walking route.server on every response. */
|
|
50
|
+
_engine;
|
|
51
|
+
/** Whether at least one header was queued. Avoids enumerating an empty object. */
|
|
52
|
+
_has_headers;
|
|
49
53
|
/** Response headers (key → value or key → string[]). */
|
|
50
54
|
_headers;
|
|
51
55
|
/** Set-Cookie headers (name → serialized cookie string). */
|
|
@@ -79,26 +83,36 @@ export default class Response {
|
|
|
79
83
|
const res = new Response();
|
|
80
84
|
res._raw_response = raw_response;
|
|
81
85
|
res.route = route;
|
|
86
|
+
res._engine = route.engine ?? null;
|
|
82
87
|
res._wrapped_request = request;
|
|
83
88
|
res._upgrade_socket = socket || null;
|
|
84
|
-
res._headers =
|
|
89
|
+
res._headers = Object.create(null);
|
|
90
|
+
res._has_headers = false;
|
|
91
|
+
res._cookies = null;
|
|
92
|
+
res._events = null;
|
|
93
|
+
res._streaming = false;
|
|
94
|
+
res._cork = false;
|
|
95
|
+
res._corked = false;
|
|
96
|
+
res._drain_handler = null;
|
|
85
97
|
res._status_code = 200;
|
|
98
|
+
res._status_message = null;
|
|
86
99
|
res._middleware_cursor = -1;
|
|
87
100
|
// handle uWS abort (client disconnects before response is sent)
|
|
88
101
|
raw_response.onAborted(() => {
|
|
89
102
|
if (res.completed)
|
|
90
103
|
return;
|
|
91
104
|
res.completed = true;
|
|
92
|
-
res.
|
|
105
|
+
res._engine?._resolve_pending_request();
|
|
93
106
|
//res._wrapped_request.pause()
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
107
|
+
const events = res._events;
|
|
108
|
+
if (events?.abort) {
|
|
109
|
+
for (let i = 0; i < events.abort.length; i++) {
|
|
110
|
+
events.abort[i](res._wrapped_request, res);
|
|
97
111
|
}
|
|
98
112
|
}
|
|
99
|
-
if (
|
|
100
|
-
for (let i = 0; i <
|
|
101
|
-
|
|
113
|
+
if (events?.close) {
|
|
114
|
+
for (let i = 0; i < events.close.length; i++) {
|
|
115
|
+
events.close[i](res._wrapped_request, res);
|
|
102
116
|
}
|
|
103
117
|
}
|
|
104
118
|
});
|
|
@@ -187,6 +201,7 @@ export default class Response {
|
|
|
187
201
|
}
|
|
188
202
|
this._headers["content-type"] =
|
|
189
203
|
mime_types.contentType(mime_type) || "text/plain";
|
|
204
|
+
this._has_headers = true;
|
|
190
205
|
return this;
|
|
191
206
|
}
|
|
192
207
|
/**
|
|
@@ -197,6 +212,7 @@ export default class Response {
|
|
|
197
212
|
name = name.toLowerCase();
|
|
198
213
|
if (overwrite || this._headers[name] === undefined) {
|
|
199
214
|
this._headers[name] = value;
|
|
215
|
+
this._has_headers = true;
|
|
200
216
|
return this;
|
|
201
217
|
}
|
|
202
218
|
const existing = this._headers[name];
|
|
@@ -269,7 +285,7 @@ export default class Response {
|
|
|
269
285
|
// perform the uWS upgrade with the stored socket handle
|
|
270
286
|
this._raw_response.upgrade({ context }, headers["sec-websocket-key"], headers["sec-websocket-protocol"], headers["sec-websocket-extensions"], this._upgrade_socket);
|
|
271
287
|
this.completed = true;
|
|
272
|
-
this.
|
|
288
|
+
this._engine?._resolve_pending_request();
|
|
273
289
|
}
|
|
274
290
|
/**
|
|
275
291
|
* Writes the status line and all accumulated headers to the uWS response.
|
|
@@ -281,31 +297,34 @@ export default class Response {
|
|
|
281
297
|
return false;
|
|
282
298
|
this.initiated = true;
|
|
283
299
|
const raw = this._raw_response;
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
300
|
+
const status_code = this._status_code;
|
|
301
|
+
const status_message = this._status_message;
|
|
302
|
+
if (status_message) {
|
|
303
|
+
raw.writeStatus(`${status_code} ${status_message}`);
|
|
304
|
+
}
|
|
305
|
+
else if (status_code !== 200) {
|
|
306
|
+
raw.writeStatus(STATUS_CACHE[status_code] || `${status_code} OK`);
|
|
307
|
+
}
|
|
308
|
+
if (this._has_headers) {
|
|
309
|
+
const headers = this._headers;
|
|
310
|
+
for (const name in headers) {
|
|
311
|
+
if (name === "content-length")
|
|
312
|
+
continue;
|
|
313
|
+
const values = headers[name];
|
|
314
|
+
if (Array.isArray(values)) {
|
|
315
|
+
for (let j = 0; j < values.length; j++) {
|
|
316
|
+
raw.writeHeader(name, values[j]);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
raw.writeHeader(name, values);
|
|
299
321
|
}
|
|
300
|
-
}
|
|
301
|
-
else {
|
|
302
|
-
raw.writeHeader(name, values);
|
|
303
322
|
}
|
|
304
323
|
}
|
|
305
324
|
if (this._cookies) {
|
|
306
|
-
const
|
|
307
|
-
for (
|
|
308
|
-
raw.writeHeader("set-cookie",
|
|
325
|
+
const cookies = this._cookies;
|
|
326
|
+
for (const name in cookies) {
|
|
327
|
+
raw.writeHeader("set-cookie", cookies[name]);
|
|
309
328
|
}
|
|
310
329
|
}
|
|
311
330
|
return true;
|
|
@@ -329,71 +348,64 @@ export default class Response {
|
|
|
329
348
|
});
|
|
330
349
|
}
|
|
331
350
|
}
|
|
351
|
+
/**
|
|
352
|
+
* Marks the response as completed and fires the lifecycle listeners.
|
|
353
|
+
* Used by every path that finishes a response, including streaming.
|
|
354
|
+
*/
|
|
355
|
+
_mark_completed() {
|
|
356
|
+
if (this.completed)
|
|
357
|
+
return;
|
|
358
|
+
const events = this._events;
|
|
359
|
+
if (!events) {
|
|
360
|
+
this.completed = true;
|
|
361
|
+
this._engine?._resolve_pending_request();
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const finish = events.finish;
|
|
365
|
+
if (!this._streaming && finish) {
|
|
366
|
+
for (let i = 0; i < finish.length; i++) {
|
|
367
|
+
finish[i](this._wrapped_request, this);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
this.completed = true;
|
|
371
|
+
this._engine?._resolve_pending_request();
|
|
372
|
+
const close = events.close;
|
|
373
|
+
if (close) {
|
|
374
|
+
for (let i = 0; i < close.length; i++) {
|
|
375
|
+
close[i](this._wrapped_request, this);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
332
379
|
/**
|
|
333
380
|
* Sends the response body and marks the response as completed.
|
|
334
381
|
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
382
|
+
* All uWS writes are wrapped in a single cork callback so status,
|
|
383
|
+
* headers and body are flushed atomically.
|
|
337
384
|
*
|
|
338
385
|
* Fires `finish` and `close` event listeners.
|
|
339
386
|
*/
|
|
340
387
|
send(body, close_connection) {
|
|
341
388
|
if (this.completed)
|
|
342
389
|
return this;
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
this._raw_response.cork(() => {
|
|
346
|
-
this._initiate_response();
|
|
347
|
-
if (body !== undefined || this._streaming) {
|
|
348
|
-
this._raw_response.end(body, close_connection);
|
|
349
|
-
}
|
|
350
|
-
else {
|
|
351
|
-
const custom_length = this._headers["content-length"];
|
|
352
|
-
if (custom_length) {
|
|
353
|
-
const content_length = typeof custom_length === "string"
|
|
354
|
-
? custom_length
|
|
355
|
-
: custom_length[custom_length.length - 1];
|
|
356
|
-
this._raw_response.endWithoutBody(content_length, close_connection);
|
|
357
|
-
}
|
|
358
|
-
else {
|
|
359
|
-
this._raw_response.end(body, close_connection);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
else {
|
|
390
|
+
const raw = this._raw_response;
|
|
391
|
+
raw.cork(() => {
|
|
365
392
|
this._initiate_response();
|
|
366
|
-
const raw = this._raw_response;
|
|
367
393
|
if (body !== undefined || this._streaming) {
|
|
368
394
|
raw.end(body, close_connection);
|
|
395
|
+
return;
|
|
369
396
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
? custom_length
|
|
375
|
-
: custom_length[custom_length.length - 1];
|
|
376
|
-
raw.endWithoutBody(content_length, close_connection);
|
|
377
|
-
}
|
|
378
|
-
else {
|
|
379
|
-
raw.end(body, close_connection);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
if (!this._streaming && this.listenerCount("finish") > 0) {
|
|
384
|
-
const handlers = this._events.finish;
|
|
385
|
-
for (let i = 0; i < handlers.length; i++) {
|
|
386
|
-
handlers[i](this._wrapped_request, this);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
this.completed = true;
|
|
390
|
-
this.engine?._resolve_pending_request();
|
|
391
|
-
if (this.listenerCount("close") > 0) {
|
|
392
|
-
const handlers = this._events.close;
|
|
393
|
-
for (let i = 0; i < handlers.length; i++) {
|
|
394
|
-
handlers[i](this._wrapped_request, this);
|
|
397
|
+
const custom_length = this._headers["content-length"];
|
|
398
|
+
if (!custom_length) {
|
|
399
|
+
raw.end(body, close_connection);
|
|
400
|
+
return;
|
|
395
401
|
}
|
|
396
|
-
|
|
402
|
+
const content_length = typeof custom_length === "string"
|
|
403
|
+
? custom_length
|
|
404
|
+
: custom_length[custom_length.length - 1];
|
|
405
|
+
raw.endWithoutBody(content_length, close_connection);
|
|
406
|
+
});
|
|
407
|
+
this._corked = true;
|
|
408
|
+
this._mark_completed();
|
|
397
409
|
return this;
|
|
398
410
|
}
|
|
399
411
|
_uws_write_chunk(chunk, total_size) {
|
|
@@ -407,17 +419,16 @@ export default class Response {
|
|
|
407
419
|
* Initiates headers if not yet sent. Returns false under backpressure.
|
|
408
420
|
*/
|
|
409
421
|
write(chunk) {
|
|
410
|
-
if (this.
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
return this._raw_response.write(chunk);
|
|
422
|
+
if (this.completed)
|
|
423
|
+
return false;
|
|
424
|
+
const raw = this._raw_response;
|
|
425
|
+
let ok = false;
|
|
426
|
+
raw.cork(() => {
|
|
427
|
+
this._initiate_response();
|
|
428
|
+
ok = raw.write(chunk);
|
|
429
|
+
});
|
|
430
|
+
this._corked = true;
|
|
431
|
+
return ok;
|
|
421
432
|
}
|
|
422
433
|
/**
|
|
423
434
|
* Streams a single chunk with backpressure support.
|
|
@@ -427,30 +438,44 @@ export default class Response {
|
|
|
427
438
|
if (this.completed)
|
|
428
439
|
return Promise.resolve();
|
|
429
440
|
return new Promise((resolve) => {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
441
|
+
const raw = this._raw_response;
|
|
442
|
+
let sent = false;
|
|
443
|
+
let responded = false;
|
|
444
|
+
let write_offset = 0;
|
|
445
|
+
raw.cork(() => {
|
|
435
446
|
this._initiate_response();
|
|
436
|
-
|
|
437
|
-
|
|
447
|
+
write_offset = raw.getWriteOffset();
|
|
448
|
+
[sent, responded] = this._uws_write_chunk(chunk, total_size);
|
|
449
|
+
if (sent || responded)
|
|
450
|
+
return;
|
|
451
|
+
this.drain((offset) => {
|
|
452
|
+
if (this.completed || !total_size) {
|
|
453
|
+
resolve();
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
const remaining = chunk.slice(offset - write_offset);
|
|
457
|
+
let flushed = false;
|
|
458
|
+
let has_responded = false;
|
|
459
|
+
raw.cork(() => {
|
|
460
|
+
;
|
|
461
|
+
[flushed, has_responded] = this._uws_write_chunk(remaining, total_size);
|
|
462
|
+
});
|
|
463
|
+
if (has_responded) {
|
|
464
|
+
this._mark_completed();
|
|
465
|
+
resolve();
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
if (flushed)
|
|
469
|
+
resolve();
|
|
470
|
+
return flushed;
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
if (responded) {
|
|
474
|
+
this._mark_completed();
|
|
438
475
|
return resolve();
|
|
439
|
-
|
|
440
|
-
const [sent] = this._uws_write_chunk(chunk, total_size);
|
|
476
|
+
}
|
|
441
477
|
if (sent)
|
|
442
|
-
|
|
443
|
-
this.drain((offset) => {
|
|
444
|
-
if (this.completed || !total_size) {
|
|
445
|
-
resolve();
|
|
446
|
-
return true;
|
|
447
|
-
}
|
|
448
|
-
const remaining = chunk.slice(offset - write_offset);
|
|
449
|
-
const [flushed] = this._uws_write_chunk(remaining, total_size);
|
|
450
|
-
if (flushed)
|
|
451
|
-
resolve();
|
|
452
|
-
return flushed;
|
|
453
|
-
});
|
|
478
|
+
resolve();
|
|
454
479
|
});
|
|
455
480
|
}
|
|
456
481
|
/**
|
|
@@ -483,7 +508,7 @@ export default class Response {
|
|
|
483
508
|
}
|
|
484
509
|
if (!this.completed) {
|
|
485
510
|
if (total_size)
|
|
486
|
-
this.
|
|
511
|
+
this._engine?._resolve_pending_request();
|
|
487
512
|
else
|
|
488
513
|
this.send();
|
|
489
514
|
}
|
|
@@ -495,7 +520,7 @@ export default class Response {
|
|
|
495
520
|
if (this.completed)
|
|
496
521
|
return;
|
|
497
522
|
this.completed = true;
|
|
498
|
-
this.
|
|
523
|
+
this._engine?._resolve_pending_request();
|
|
499
524
|
//this._wrapped_request.pause()
|
|
500
525
|
this._raw_response.close();
|
|
501
526
|
}
|
|
@@ -509,24 +534,18 @@ export default class Response {
|
|
|
509
534
|
}
|
|
510
535
|
/**
|
|
511
536
|
* Fast-path send used by the `_invokeHandler`.
|
|
512
|
-
*
|
|
537
|
+
* Corks the writes so status, headers and body are flushed atomically.
|
|
513
538
|
*/
|
|
514
539
|
_sendFast(body) {
|
|
540
|
+
if (this.completed)
|
|
541
|
+
return;
|
|
542
|
+
const raw = this._raw_response;
|
|
543
|
+
raw.cork(() => {
|
|
544
|
+
this._initiate_response();
|
|
545
|
+
raw.end(body);
|
|
546
|
+
});
|
|
515
547
|
this._corked = true;
|
|
516
|
-
this.
|
|
517
|
-
this._raw_response.end(body);
|
|
518
|
-
this.completed = true;
|
|
519
|
-
this.engine?._resolve_pending_request();
|
|
520
|
-
if (this._events?.finish) {
|
|
521
|
-
for (let i = 0; i < this._events.finish.length; i++) {
|
|
522
|
-
this._events.finish[i](this._wrapped_request, this);
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
if (this._events?.close) {
|
|
526
|
-
for (let i = 0; i < this._events.close.length; i++) {
|
|
527
|
-
this._events.close[i](this._wrapped_request, this);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
548
|
+
this._mark_completed();
|
|
530
549
|
}
|
|
531
550
|
/**
|
|
532
551
|
* Sends a JSON response. Sets `Content-Type: application/json` and
|
|
@@ -534,6 +553,7 @@ export default class Response {
|
|
|
534
553
|
*/
|
|
535
554
|
json(body) {
|
|
536
555
|
this._headers["content-type"] = "application/json";
|
|
556
|
+
this._has_headers = true;
|
|
537
557
|
return this.send(stringify(body));
|
|
538
558
|
}
|
|
539
559
|
/**
|
|
@@ -541,6 +561,7 @@ export default class Response {
|
|
|
541
561
|
*/
|
|
542
562
|
html(body) {
|
|
543
563
|
this._headers["content-type"] = "text/html";
|
|
564
|
+
this._has_headers = true;
|
|
544
565
|
return this.send(body);
|
|
545
566
|
}
|
|
546
567
|
/**
|
|
@@ -711,6 +732,7 @@ export default class Response {
|
|
|
711
732
|
*/
|
|
712
733
|
location(path) {
|
|
713
734
|
this._headers["location"] = path;
|
|
735
|
+
this._has_headers = true;
|
|
714
736
|
return this;
|
|
715
737
|
}
|
|
716
738
|
/**
|
|
@@ -737,6 +759,7 @@ export default class Response {
|
|
|
737
759
|
*/
|
|
738
760
|
vary(name) {
|
|
739
761
|
this._headers["vary"] = name;
|
|
762
|
+
this._has_headers = true;
|
|
740
763
|
return this;
|
|
741
764
|
}
|
|
742
765
|
}
|