@linebridge/engine-neo 1.1.0 → 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/README.md +2 -3
- package/lib/engine.d.ts +5 -3
- package/lib/engine.js +14 -9
- package/lib/listen.js +3 -1
- package/lib/request_iterator.d.ts +2 -2
- package/lib/response.d.ts +12 -3
- package/lib/response.js +153 -130
- package/lib/route_register.d.ts +3 -3
- package/lib/route_register.js +14 -48
- package/lib/utils.d.ts +1 -1
- package/lib/ws/Websocket.d.ts +70 -118
- package/lib/ws/Websocket.js +81 -209
- package/lib/ws/WebsocketRoute.d.ts +86 -0
- package/lib/ws/WebsocketRoute.js +139 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ The official, high-performance HTTP and WebSocket engine for [Linebridge](https:
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
8
|
+
pnpm add @linebridge/engine-neo
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
@@ -22,5 +22,4 @@ import "@linebridge/engine-neo"
|
|
|
22
22
|
|
|
23
23
|
This project is licensed under the **MIT License**.
|
|
24
24
|
|
|
25
|
-
It includes software developed by uNetworking AB and uWebSockets.js contributors, which is licensed under the **Apache License 2.0**.
|
|
26
|
-
See the [LICENSE](./LICENSE) file for more details.
|
|
25
|
+
It includes software developed by uNetworking AB and uWebSockets.js contributors, which is licensed under the **Apache License 2.0**. See the [LICENSE](./LICENSE) file for more details.
|
package/lib/engine.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ import uws_wrapper, { type uWebsockets } from "./uws.js";
|
|
|
15
15
|
import RTEngine from "linebridge/classes/RtEngine/index";
|
|
16
16
|
import { EngineAdaptor } from "linebridge/classes/EngineAdaptor/index";
|
|
17
17
|
import { Route } from "linebridge/classes/Route/index";
|
|
18
|
+
import route_register from "./route_register.js";
|
|
19
|
+
import middleware_register from "./register_middleware.js";
|
|
18
20
|
/**
|
|
19
21
|
* Options that control the engine's behaviour.
|
|
20
22
|
*/
|
|
@@ -76,9 +78,9 @@ export declare class NeoEngine extends EngineAdaptor {
|
|
|
76
78
|
/** Gracefully shuts down the server. Bound to the engine instance. */
|
|
77
79
|
close: () => Promise<boolean>;
|
|
78
80
|
/** Registers a route (HTTP method + path + handler). */
|
|
79
|
-
register:
|
|
81
|
+
register: OmitThisParameter<typeof route_register>;
|
|
80
82
|
/** Registers a global middleware that runs before every route handler. */
|
|
81
|
-
register_middleware:
|
|
83
|
+
register_middleware: OmitThisParameter<typeof middleware_register>;
|
|
82
84
|
/**
|
|
83
85
|
* Publish a message to a topic in MQTT syntax to all WebSocket connections on this Server instance.
|
|
84
86
|
* You cannot publish using wildcards, only fully specified topics.
|
|
@@ -91,7 +93,7 @@ export declare class NeoEngine extends EngineAdaptor {
|
|
|
91
93
|
/** Incoming HTTP request handler. Bound to the engine instance. */
|
|
92
94
|
on_request: (native_req: uWebsockets.HttpRequest, native_res: uWebsockets.HttpResponse, route: Route<any, "server"[]>, socket?: uWebsockets.us_socket_context_t | null | undefined) => any;
|
|
93
95
|
/** Middleware/route iteration loop. Bound to the engine instance. */
|
|
94
|
-
request_iterator: (request: Request<any>, response: Response<any>, route: Route<Server<"neo">, "server"[]>, allMiddlewares: Handler<HandlerKind>[], cursor?: number | undefined) => Promise<void>;
|
|
96
|
+
request_iterator: (request: Request<any>, response: Response<any>, route: Route<Server<"neo">, "server"[]>, allMiddlewares: Handler<HandlerKind.middleware>[], cursor?: number | undefined) => Promise<void>;
|
|
95
97
|
/**
|
|
96
98
|
* Decrements the pending request counter and fires the zero-handler
|
|
97
99
|
* when no requests remain in flight.
|
package/lib/engine.js
CHANGED
|
@@ -14,7 +14,7 @@ import toBoolean from "linebridge/utils/toBoolean";
|
|
|
14
14
|
import RTEngine from "linebridge/classes/RtEngine/index";
|
|
15
15
|
import { EngineAdaptor } from "linebridge/classes/EngineAdaptor/index";
|
|
16
16
|
import { Route } from "linebridge/classes/Route/index";
|
|
17
|
-
import
|
|
17
|
+
import Websocket from "./ws/Websocket.js";
|
|
18
18
|
import route_register from "./route_register.js";
|
|
19
19
|
import middleware_register from "./register_middleware.js";
|
|
20
20
|
import listen from "./listen.js";
|
|
@@ -128,7 +128,7 @@ export class NeoEngine extends EngineAdaptor {
|
|
|
128
128
|
upgrade: (res, req, _context) => this.ws.handleUpgrade(req, res),
|
|
129
129
|
// wrap raw uWS websocket with the polyfill so RTEngine gets EventEmitter + helpers
|
|
130
130
|
open: (ws) => {
|
|
131
|
-
const poly = new
|
|
131
|
+
const poly = new Websocket(ws);
|
|
132
132
|
ws.poly = poly;
|
|
133
133
|
//@ts-ignore polyfill context is Object vs RtEngineSocket's typed shape, but runtime is correct
|
|
134
134
|
this.ws.handleConnection(poly);
|
|
@@ -151,12 +151,15 @@ export class NeoEngine extends EngineAdaptor {
|
|
|
151
151
|
// @ts-ignore
|
|
152
152
|
global.websockets = this.ws;
|
|
153
153
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
154
|
+
if (!toBoolean(process.env.LB_SOCKET_MODE)) {
|
|
155
|
+
// create the default route
|
|
156
|
+
const defaultRoute = new Route();
|
|
157
|
+
defaultRoute.method = "any";
|
|
158
|
+
defaultRoute.path = "/*";
|
|
159
|
+
defaultRoute.fn = this._defaultResponse;
|
|
160
|
+
defaultRoute._initialize(this.server);
|
|
161
|
+
this.register(defaultRoute);
|
|
162
|
+
}
|
|
160
163
|
};
|
|
161
164
|
/** Starts the server. Bound to the engine instance. */
|
|
162
165
|
listen = listen.bind(this);
|
|
@@ -200,7 +203,9 @@ export class NeoEngine extends EngineAdaptor {
|
|
|
200
203
|
* Default handler for unmatched routes. Responds with a 404 JSON body.
|
|
201
204
|
*/
|
|
202
205
|
_defaultResponse(req, res) {
|
|
203
|
-
res.status(404)
|
|
206
|
+
res.status(404)
|
|
207
|
+
.setHeader("content-type", "application/json")
|
|
208
|
+
._sendFast(`{"error":"Not found"}`);
|
|
204
209
|
}
|
|
205
210
|
}
|
|
206
211
|
export { uws_wrapper as uWebsockets };
|
package/lib/listen.js
CHANGED
|
@@ -25,7 +25,9 @@ export default async function () {
|
|
|
25
25
|
this.listen_socket = listen_socket;
|
|
26
26
|
// optionally register process-exit listeners for graceful shutdown
|
|
27
27
|
if (this.options.auto_close) {
|
|
28
|
-
EXIT_EVENTS.forEach((type) => process.once(type, () =>
|
|
28
|
+
EXIT_EVENTS.forEach((type) => process.once(type, () => {
|
|
29
|
+
void this.server._fireClose();
|
|
30
|
+
}));
|
|
29
31
|
}
|
|
30
32
|
resolve();
|
|
31
33
|
}
|
|
@@ -9,7 +9,7 @@ import type Engine from "./engine.js";
|
|
|
9
9
|
import type Request from "./request.js";
|
|
10
10
|
import type Response from "./response.js";
|
|
11
11
|
import type { Route } from "linebridge/classes/Route/index";
|
|
12
|
-
import type { Handler } from "linebridge/classes/Handler/index";
|
|
12
|
+
import type { Handler, HandlerKind } from "linebridge/classes/Handler/index";
|
|
13
13
|
/**
|
|
14
14
|
* Recursively iterates the middleware chain, then calls the route handler.
|
|
15
15
|
*
|
|
@@ -19,4 +19,4 @@ import type { Handler } from "linebridge/classes/Handler/index";
|
|
|
19
19
|
*
|
|
20
20
|
* @param cursor - Zero-based index into the middleware array. Increments on each recursion.
|
|
21
21
|
*/
|
|
22
|
-
export default function (this: Engine, request: Request<any>, response: Response<any>, route: Route<typeof this.server>, allMiddlewares: Handler[], cursor?: number): Promise<void>;
|
|
22
|
+
export default function (this: Engine, request: Request<any>, response: Response<any>, route: Route<typeof this.server>, allMiddlewares: Handler<HandlerKind.middleware>[], cursor?: number): Promise<void>;
|
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
|
}
|
package/lib/route_register.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import {
|
|
1
|
+
import type { Route } from "linebridge/classes/Route/index";
|
|
2
|
+
import type { NeoEngine } from "./engine.js";
|
|
3
3
|
/**
|
|
4
4
|
* Registers a route with the engine's uWS app.
|
|
5
5
|
*
|
|
@@ -7,4 +7,4 @@ import { RouteAlike } from "linebridge/classes/Route/index";
|
|
|
7
7
|
*
|
|
8
8
|
* @throws {Error} If the engine is not initialized or the route is invalid.
|
|
9
9
|
*/
|
|
10
|
-
export default function (this:
|
|
10
|
+
export default function (this: NeoEngine, route: Route): void;
|