@kevisual/router 0.0.42 → 0.0.44
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/dist/router.d.ts +64 -29
- package/dist/router.js +91 -4
- package/package.json +1 -1
- package/src/app.ts +1 -1
- package/src/index.ts +3 -0
- package/src/server/server-base.ts +7 -5
- package/src/server/server-bun.ts +96 -18
- package/src/server/server-type.ts +43 -17
package/dist/router.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as http from 'node:http';
|
|
2
|
+
import http__default, { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
3
|
import https from 'node:https';
|
|
3
4
|
import http2 from 'node:http2';
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
5
|
+
import EventEmitter from 'node:events';
|
|
6
|
+
import { EventEmitter as EventEmitter$1 } from 'events';
|
|
6
7
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
7
8
|
import { z } from 'zod';
|
|
9
|
+
import { IncomingMessage as IncomingMessage$1, ServerResponse as ServerResponse$1 } from 'http';
|
|
8
10
|
import { RouteOpts as RouteOpts$1, QueryRouterServer as QueryRouterServer$1, RouteMiddleware as RouteMiddleware$1, Run as Run$1 } from '@kevisual/router';
|
|
9
11
|
import { Query, DataOpts, Result } from '@kevisual/query/query';
|
|
10
12
|
|
|
@@ -383,14 +385,6 @@ declare class QueryConnect {
|
|
|
383
385
|
}[];
|
|
384
386
|
}
|
|
385
387
|
|
|
386
|
-
type Listener$1 = {
|
|
387
|
-
id?: string;
|
|
388
|
-
io?: boolean;
|
|
389
|
-
path?: string;
|
|
390
|
-
fun: (...args: any[]) => Promise<void> | void;
|
|
391
|
-
};
|
|
392
|
-
type ListenerFun = (...args: any[]) => Promise<void> | void;
|
|
393
|
-
type OnListener = Listener$1 | Listener$1[] | ListenerFun | ListenerFun[];
|
|
394
388
|
type Cors$2 = {
|
|
395
389
|
/**
|
|
396
390
|
* @default '*''
|
|
@@ -432,18 +426,64 @@ interface ServerType {
|
|
|
432
426
|
* @param listener
|
|
433
427
|
*/
|
|
434
428
|
on(listener: OnListener): void;
|
|
435
|
-
onWebSocket({ ws, message, pathname, token, id }:
|
|
436
|
-
ws: WS;
|
|
437
|
-
message: string | Buffer;
|
|
438
|
-
pathname: string;
|
|
439
|
-
token?: string;
|
|
440
|
-
id?: string;
|
|
441
|
-
}): void;
|
|
429
|
+
onWebSocket({ ws, message, pathname, token, id }: OnWebSocketOptions): void;
|
|
442
430
|
}
|
|
431
|
+
type OnWebSocketOptions = {
|
|
432
|
+
ws: WS;
|
|
433
|
+
message: string | Buffer;
|
|
434
|
+
pathname: string;
|
|
435
|
+
token?: string;
|
|
436
|
+
id?: string;
|
|
437
|
+
};
|
|
438
|
+
type OnWebSocketFn = (options: OnWebSocketOptions) => Promise<void> | void;
|
|
443
439
|
type WS = {
|
|
444
440
|
send: (data: any) => void;
|
|
445
|
-
close: () => void;
|
|
441
|
+
close: (code?: number, reason?: string) => void;
|
|
442
|
+
};
|
|
443
|
+
type Listener$1 = {
|
|
444
|
+
id?: string;
|
|
445
|
+
io?: boolean;
|
|
446
|
+
path?: string;
|
|
447
|
+
fun: WebScoketListenerFun | HttpListenerFun;
|
|
448
|
+
};
|
|
449
|
+
type WebScoketListenerFun = (req: WebSocketReq, res: WebSocketRes) => Promise<void> | void;
|
|
450
|
+
type HttpListenerFun = (req: RouterReq, res: RouterRes) => Promise<void> | void;
|
|
451
|
+
type WebSocketReq = {
|
|
452
|
+
emitter?: EventEmitter;
|
|
453
|
+
ws: WS;
|
|
454
|
+
data: any;
|
|
455
|
+
pathname?: string;
|
|
456
|
+
token?: string;
|
|
457
|
+
id?: string;
|
|
446
458
|
};
|
|
459
|
+
type WebSocketRes = {
|
|
460
|
+
end: (data: any) => void;
|
|
461
|
+
};
|
|
462
|
+
type ListenerFun = WebScoketListenerFun | HttpListenerFun;
|
|
463
|
+
type OnListener = Listener$1 | ListenerFun | (Listener$1 | ListenerFun)[];
|
|
464
|
+
type RouterReq<T = {}> = {
|
|
465
|
+
url: string;
|
|
466
|
+
method: string;
|
|
467
|
+
headers: Record<string, string>;
|
|
468
|
+
socket?: {
|
|
469
|
+
remoteAddress?: string;
|
|
470
|
+
remotePort?: number;
|
|
471
|
+
};
|
|
472
|
+
cookies?: Record<string, string>;
|
|
473
|
+
} & T;
|
|
474
|
+
type RouterRes<T = {}> = {
|
|
475
|
+
statusCode: number;
|
|
476
|
+
headersSent: boolean;
|
|
477
|
+
_headers: Record<string, string | string[]>;
|
|
478
|
+
_bodyChunks: any[];
|
|
479
|
+
writableEnded: boolean;
|
|
480
|
+
writeHead: (statusCode: number, headers?: Record<string, string>) => void;
|
|
481
|
+
setHeader: (name: string, value: string | string[]) => void;
|
|
482
|
+
cookie: (name: string, value: string, options?: any) => void;
|
|
483
|
+
write: (chunk: any) => void;
|
|
484
|
+
pipe: (stream: any) => void;
|
|
485
|
+
end: (data?: any) => void;
|
|
486
|
+
} & T;
|
|
447
487
|
|
|
448
488
|
interface StringifyOptions {
|
|
449
489
|
/**
|
|
@@ -565,6 +605,7 @@ declare class ServerBase implements ServerType {
|
|
|
565
605
|
_callback: any;
|
|
566
606
|
cors: Cors$1;
|
|
567
607
|
listeners: Listener$1[];
|
|
608
|
+
emitter: EventEmitter$1<any>;
|
|
568
609
|
constructor(opts?: ServerOpts);
|
|
569
610
|
listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void): void;
|
|
570
611
|
listen(port: number, hostname?: string, listeningListener?: () => void): void;
|
|
@@ -590,13 +631,7 @@ declare class ServerBase implements ServerType {
|
|
|
590
631
|
*/
|
|
591
632
|
createCallback(): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
592
633
|
on(listener: OnListener): void;
|
|
593
|
-
onWebSocket({ ws, message, pathname, token, id }:
|
|
594
|
-
ws: any;
|
|
595
|
-
message: any;
|
|
596
|
-
pathname: any;
|
|
597
|
-
token: any;
|
|
598
|
-
id: any;
|
|
599
|
-
}): Promise<void>;
|
|
634
|
+
onWebSocket({ ws, message, pathname, token, id }: OnWebSocketOptions): Promise<void>;
|
|
600
635
|
}
|
|
601
636
|
|
|
602
637
|
type WsServerBaseOpts = {
|
|
@@ -638,7 +673,7 @@ type ServerNodeOpts = ServerOpts<{
|
|
|
638
673
|
httpsCert?: string;
|
|
639
674
|
}>;
|
|
640
675
|
declare class ServerNode extends ServerBase implements ServerType {
|
|
641
|
-
_server:
|
|
676
|
+
_server: http__default.Server | https.Server | http2.Http2SecureServer;
|
|
642
677
|
_callback: any;
|
|
643
678
|
cors: Cors;
|
|
644
679
|
private httpType;
|
|
@@ -647,7 +682,7 @@ declare class ServerNode extends ServerBase implements ServerType {
|
|
|
647
682
|
io: WsServer | undefined;
|
|
648
683
|
constructor(opts?: ServerNodeOpts);
|
|
649
684
|
customListen(...args: any[]): void;
|
|
650
|
-
createServer():
|
|
685
|
+
createServer(): http__default.Server<typeof http__default.IncomingMessage, typeof http__default.ServerResponse> | http2.Http2SecureServer<typeof http__default.IncomingMessage, typeof http__default.ServerResponse, typeof http2.Http2ServerRequest, typeof http2.Http2ServerResponse>;
|
|
651
686
|
}
|
|
652
687
|
|
|
653
688
|
/**
|
|
@@ -889,4 +924,4 @@ declare class App<U = {}> {
|
|
|
889
924
|
}
|
|
890
925
|
|
|
891
926
|
export { App, Connect, CustomError, Mini, QueryConnect, QueryRouter, QueryRouterServer, QueryUtil, Route, ServerNode, createSchema, define, handleServer, util };
|
|
892
|
-
export type { RouteArray, RouteContext, RouteMiddleware, RouteObject, RouteOpts, Rule, Run, Schema };
|
|
927
|
+
export type { OnWebSocketFn, RouteArray, RouteContext, RouteMiddleware, RouteObject, RouteOpts, RouterReq, RouterRes, Rule, Run, Schema };
|
package/dist/router.js
CHANGED
|
@@ -3,7 +3,7 @@ import require$$2 from 'node:http';
|
|
|
3
3
|
import require$$1$1 from 'node:https';
|
|
4
4
|
import http2 from 'node:http2';
|
|
5
5
|
import url from 'node:url';
|
|
6
|
-
import require$$0$3 from 'node:events';
|
|
6
|
+
import require$$0$3, { EventEmitter } from 'node:events';
|
|
7
7
|
import require$$3 from 'node:net';
|
|
8
8
|
import require$$4 from 'node:tls';
|
|
9
9
|
import require$$0$2 from 'node:stream';
|
|
@@ -1214,6 +1214,7 @@ class ServerBase {
|
|
|
1214
1214
|
_callback;
|
|
1215
1215
|
cors;
|
|
1216
1216
|
listeners = [];
|
|
1217
|
+
emitter = new EventEmitter();
|
|
1217
1218
|
constructor(opts) {
|
|
1218
1219
|
this.path = opts?.path || '/api/router';
|
|
1219
1220
|
this.handle = opts?.handle;
|
|
@@ -1348,6 +1349,7 @@ class ServerBase {
|
|
|
1348
1349
|
ws.send(JSON.stringify(data));
|
|
1349
1350
|
};
|
|
1350
1351
|
listener.fun({
|
|
1352
|
+
emitter: this.emitter,
|
|
1351
1353
|
data,
|
|
1352
1354
|
token,
|
|
1353
1355
|
id,
|
|
@@ -6498,6 +6500,7 @@ class BunServer extends ServerBase {
|
|
|
6498
6500
|
idleTimeout: 0, // 4 minutes idle timeout (max 255 seconds)
|
|
6499
6501
|
fetch: async (request, server) => {
|
|
6500
6502
|
const host = request.headers.get('host') || 'localhost';
|
|
6503
|
+
const clientInfo = server.requestIP(request); // 返回 { address: string, port: number } 或 null
|
|
6501
6504
|
const url = new URL(request.url, `http://${host}`);
|
|
6502
6505
|
// 处理 WebSocket 升级请求
|
|
6503
6506
|
if (request.headers.get('upgrade') === 'websocket') {
|
|
@@ -6520,12 +6523,18 @@ class BunServer extends ServerBase {
|
|
|
6520
6523
|
url: url.pathname + url.search,
|
|
6521
6524
|
method: request.method,
|
|
6522
6525
|
headers: Object.fromEntries(request.headers.entries()),
|
|
6526
|
+
socket: {
|
|
6527
|
+
// @ts-ignore
|
|
6528
|
+
remoteAddress: request?.remoteAddress || request?.ip || clientInfo?.address || '',
|
|
6529
|
+
remotePort: clientInfo?.port || 0,
|
|
6530
|
+
}
|
|
6523
6531
|
};
|
|
6524
6532
|
const res = {
|
|
6525
6533
|
statusCode: 200,
|
|
6526
6534
|
headersSent: false,
|
|
6527
6535
|
writableEnded: false,
|
|
6528
6536
|
_headers: {},
|
|
6537
|
+
_bodyChunks: [],
|
|
6529
6538
|
writeHead(statusCode, headers) {
|
|
6530
6539
|
this.statusCode = statusCode;
|
|
6531
6540
|
for (const key in headers) {
|
|
@@ -6563,9 +6572,79 @@ class BunServer extends ServerBase {
|
|
|
6563
6572
|
}
|
|
6564
6573
|
this.setHeader('Set-Cookie', cookieString);
|
|
6565
6574
|
},
|
|
6575
|
+
write(chunk, encoding, callback) {
|
|
6576
|
+
if (typeof encoding === 'function') {
|
|
6577
|
+
callback = encoding;
|
|
6578
|
+
encoding = 'utf8';
|
|
6579
|
+
}
|
|
6580
|
+
if (!this._bodyChunks) {
|
|
6581
|
+
this._bodyChunks = [];
|
|
6582
|
+
}
|
|
6583
|
+
this._bodyChunks.push(chunk);
|
|
6584
|
+
if (callback)
|
|
6585
|
+
callback();
|
|
6586
|
+
return true;
|
|
6587
|
+
},
|
|
6588
|
+
pipe(stream) {
|
|
6589
|
+
this.writableEnded = true;
|
|
6590
|
+
// 如果是 ReadableStream,直接使用
|
|
6591
|
+
if (stream instanceof ReadableStream) {
|
|
6592
|
+
resolve(new Response(stream, {
|
|
6593
|
+
status: this.statusCode,
|
|
6594
|
+
headers: this._headers,
|
|
6595
|
+
}));
|
|
6596
|
+
return;
|
|
6597
|
+
}
|
|
6598
|
+
// 如果是 Node.js 流,转换为 ReadableStream
|
|
6599
|
+
const readableStream = new ReadableStream({
|
|
6600
|
+
start(controller) {
|
|
6601
|
+
stream.on('data', (chunk) => {
|
|
6602
|
+
controller.enqueue(chunk);
|
|
6603
|
+
});
|
|
6604
|
+
stream.on('end', () => {
|
|
6605
|
+
controller.close();
|
|
6606
|
+
});
|
|
6607
|
+
stream.on('error', (err) => {
|
|
6608
|
+
controller.error(err);
|
|
6609
|
+
});
|
|
6610
|
+
},
|
|
6611
|
+
cancel() {
|
|
6612
|
+
if (stream.destroy) {
|
|
6613
|
+
stream.destroy();
|
|
6614
|
+
}
|
|
6615
|
+
}
|
|
6616
|
+
});
|
|
6617
|
+
resolve(new Response(readableStream, {
|
|
6618
|
+
status: this.statusCode,
|
|
6619
|
+
headers: this._headers,
|
|
6620
|
+
}));
|
|
6621
|
+
},
|
|
6566
6622
|
end(data) {
|
|
6567
6623
|
this.writableEnded = true;
|
|
6568
|
-
|
|
6624
|
+
// 合并所有写入的数据块
|
|
6625
|
+
let responseData = data;
|
|
6626
|
+
if (this._bodyChunks && this._bodyChunks.length > 0) {
|
|
6627
|
+
if (data)
|
|
6628
|
+
this._bodyChunks.push(data);
|
|
6629
|
+
// 处理 Buffer 和字符串混合的情况
|
|
6630
|
+
const hasBuffer = this._bodyChunks.some(chunk => chunk instanceof Buffer || chunk instanceof Uint8Array);
|
|
6631
|
+
if (hasBuffer) {
|
|
6632
|
+
// 如果有 Buffer,转换所有内容为 Buffer 后合并
|
|
6633
|
+
const buffers = this._bodyChunks.map(chunk => {
|
|
6634
|
+
if (chunk instanceof Buffer)
|
|
6635
|
+
return chunk;
|
|
6636
|
+
if (chunk instanceof Uint8Array)
|
|
6637
|
+
return Buffer.from(chunk);
|
|
6638
|
+
return Buffer.from(String(chunk));
|
|
6639
|
+
});
|
|
6640
|
+
responseData = Buffer.concat(buffers);
|
|
6641
|
+
}
|
|
6642
|
+
else {
|
|
6643
|
+
// 纯字符串,直接拼接
|
|
6644
|
+
responseData = this._bodyChunks.map(chunk => String(chunk)).join('');
|
|
6645
|
+
}
|
|
6646
|
+
}
|
|
6647
|
+
resolve(new Response(responseData, {
|
|
6569
6648
|
status: this.statusCode,
|
|
6570
6649
|
headers: this._headers,
|
|
6571
6650
|
}));
|
|
@@ -6585,7 +6664,7 @@ class BunServer extends ServerBase {
|
|
|
6585
6664
|
},
|
|
6586
6665
|
websocket: {
|
|
6587
6666
|
open: (ws) => {
|
|
6588
|
-
ws.send('connected');
|
|
6667
|
+
ws.send({ type: 'connected' });
|
|
6589
6668
|
},
|
|
6590
6669
|
message: async (ws, message) => {
|
|
6591
6670
|
const pathname = ws.data.pathname || '';
|
|
@@ -6595,6 +6674,14 @@ class BunServer extends ServerBase {
|
|
|
6595
6674
|
},
|
|
6596
6675
|
close: (ws) => {
|
|
6597
6676
|
// WebSocket 连接关闭
|
|
6677
|
+
const id = ws?.data?.id || '';
|
|
6678
|
+
if (id) {
|
|
6679
|
+
this.emitter.emit(id, { type: 'close', ws, id });
|
|
6680
|
+
setTimeout(() => {
|
|
6681
|
+
// 关闭后 5 秒清理监听器, 避免内存泄漏, 理论上原本的自己就应该被清理掉了,这里是保险起见
|
|
6682
|
+
this.emitter.removeAllListeners(id);
|
|
6683
|
+
}, 5000);
|
|
6684
|
+
}
|
|
6598
6685
|
},
|
|
6599
6686
|
},
|
|
6600
6687
|
});
|
|
@@ -11081,7 +11168,7 @@ class App {
|
|
|
11081
11168
|
}
|
|
11082
11169
|
this.server.on({
|
|
11083
11170
|
id: 'app-request-listener',
|
|
11084
|
-
fun: fn
|
|
11171
|
+
fun: fn,
|
|
11085
11172
|
});
|
|
11086
11173
|
}
|
|
11087
11174
|
}
|
package/package.json
CHANGED
package/src/app.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
2
|
import { handleServer } from './handle-server.ts';
|
|
3
3
|
import * as cookie from './cookie.ts';
|
|
4
|
-
import { ServerType, Listener, OnListener, ServerOpts } from './server-type.ts';
|
|
4
|
+
import { ServerType, Listener, OnListener, ServerOpts, OnWebSocketOptions, OnWebSocketFn, WebScoketListenerFun, ListenerFun, HttpListenerFun } from './server-type.ts';
|
|
5
5
|
import { parseIfJson } from '../utils/parse.ts';
|
|
6
|
-
|
|
6
|
+
import { EventEmitter } from 'events';
|
|
7
7
|
type CookieFn = (name: string, value: string, options?: cookie.SerializeOptions, end?: boolean) => void;
|
|
8
8
|
|
|
9
9
|
export type HandleCtx = {
|
|
@@ -63,6 +63,7 @@ export class ServerBase implements ServerType {
|
|
|
63
63
|
_callback: any;
|
|
64
64
|
cors: Cors;
|
|
65
65
|
listeners: Listener[] = [];
|
|
66
|
+
emitter = new EventEmitter();
|
|
66
67
|
constructor(opts?: ServerOpts) {
|
|
67
68
|
this.path = opts?.path || '/api/router';
|
|
68
69
|
this.handle = opts?.handle;
|
|
@@ -118,7 +119,7 @@ export class ServerBase implements ServerType {
|
|
|
118
119
|
}
|
|
119
120
|
const listeners = that.listeners || [];
|
|
120
121
|
for (const item of listeners) {
|
|
121
|
-
const fun = item.fun;
|
|
122
|
+
const fun = item.fun as any;
|
|
122
123
|
if (typeof fun === 'function' && !item.io) {
|
|
123
124
|
await fun(req, res);
|
|
124
125
|
}
|
|
@@ -193,7 +194,7 @@ export class ServerBase implements ServerType {
|
|
|
193
194
|
this.listeners.push(listener);
|
|
194
195
|
}
|
|
195
196
|
}
|
|
196
|
-
async onWebSocket({ ws, message, pathname, token, id }) {
|
|
197
|
+
async onWebSocket({ ws, message, pathname, token, id }: OnWebSocketOptions) {
|
|
197
198
|
const listener = this.listeners.find((item) => item.path === pathname && item.io);
|
|
198
199
|
const data: any = parseIfJson(message);
|
|
199
200
|
|
|
@@ -201,7 +202,8 @@ export class ServerBase implements ServerType {
|
|
|
201
202
|
const end = (data: any) => {
|
|
202
203
|
ws.send(JSON.stringify(data));
|
|
203
204
|
}
|
|
204
|
-
listener.fun({
|
|
205
|
+
(listener.fun as WebScoketListenerFun)({
|
|
206
|
+
emitter: this.emitter,
|
|
205
207
|
data,
|
|
206
208
|
token,
|
|
207
209
|
id,
|
package/src/server/server-bun.ts
CHANGED
|
@@ -4,19 +4,8 @@
|
|
|
4
4
|
* @tags bun, server, websocket, http
|
|
5
5
|
* @createdAt 2025-12-20
|
|
6
6
|
*/
|
|
7
|
-
import type
|
|
8
|
-
import { ServerType, type ServerOpts, type Cors, Listener } from './server-type.ts';
|
|
9
|
-
import { handleServer } from './handle-server.ts';
|
|
7
|
+
import { ServerType, type ServerOpts, type Cors, RouterRes, RouterReq } from './server-type.ts';
|
|
10
8
|
import { ServerBase } from './server-base.ts';
|
|
11
|
-
import { parseIfJson } from '../utils/parse.ts';
|
|
12
|
-
|
|
13
|
-
const resultError = (error: string, code = 500) => {
|
|
14
|
-
const r = {
|
|
15
|
-
code: code,
|
|
16
|
-
message: error,
|
|
17
|
-
};
|
|
18
|
-
return JSON.stringify(r);
|
|
19
|
-
};
|
|
20
9
|
|
|
21
10
|
export class BunServer extends ServerBase implements ServerType {
|
|
22
11
|
declare _server: any;
|
|
@@ -54,15 +43,16 @@ export class BunServer extends ServerBase implements ServerType {
|
|
|
54
43
|
}
|
|
55
44
|
}
|
|
56
45
|
|
|
57
|
-
const requestCallback = this.createCallback();
|
|
46
|
+
const requestCallback = this.createCallback() as unknown as (req: RouterReq, res: RouterRes) => void;
|
|
58
47
|
const wsPath = this.path;
|
|
59
48
|
// @ts-ignore
|
|
60
49
|
this._server = Bun.serve({
|
|
61
50
|
port,
|
|
62
51
|
hostname,
|
|
63
52
|
idleTimeout: 0, // 4 minutes idle timeout (max 255 seconds)
|
|
64
|
-
fetch: async (request:
|
|
53
|
+
fetch: async (request: Bun.BunRequest, server: any) => {
|
|
65
54
|
const host = request.headers.get('host') || 'localhost';
|
|
55
|
+
const clientInfo = server.requestIP(request); // 返回 { address: string, port: number } 或 null
|
|
66
56
|
const url = new URL(request.url, `http://${host}`);
|
|
67
57
|
// 处理 WebSocket 升级请求
|
|
68
58
|
if (request.headers.get('upgrade') === 'websocket') {
|
|
@@ -82,17 +72,23 @@ export class BunServer extends ServerBase implements ServerType {
|
|
|
82
72
|
|
|
83
73
|
// 将 Bun 的 Request 转换为 Node.js 风格的 req/res
|
|
84
74
|
return new Promise((resolve) => {
|
|
85
|
-
const req:
|
|
75
|
+
const req: RouterReq = {
|
|
86
76
|
url: url.pathname + url.search,
|
|
87
77
|
method: request.method,
|
|
88
78
|
headers: Object.fromEntries(request.headers.entries()),
|
|
79
|
+
socket: {
|
|
80
|
+
// @ts-ignore
|
|
81
|
+
remoteAddress: request?.remoteAddress || request?.ip || clientInfo?.address || '',
|
|
82
|
+
remotePort: clientInfo?.port || 0,
|
|
83
|
+
}
|
|
89
84
|
};
|
|
90
85
|
|
|
91
|
-
const res:
|
|
86
|
+
const res: RouterRes = {
|
|
92
87
|
statusCode: 200,
|
|
93
88
|
headersSent: false,
|
|
94
89
|
writableEnded: false,
|
|
95
90
|
_headers: {} as Record<string, string | string[]>,
|
|
91
|
+
_bodyChunks: [] as any[],
|
|
96
92
|
writeHead(statusCode: number, headers: Record<string, string | string[]>) {
|
|
97
93
|
this.statusCode = statusCode;
|
|
98
94
|
for (const key in headers) {
|
|
@@ -130,10 +126,84 @@ export class BunServer extends ServerBase implements ServerType {
|
|
|
130
126
|
}
|
|
131
127
|
this.setHeader('Set-Cookie', cookieString);
|
|
132
128
|
},
|
|
129
|
+
write(chunk: any, encoding?: string | Function, callback?: Function) {
|
|
130
|
+
if (typeof encoding === 'function') {
|
|
131
|
+
callback = encoding;
|
|
132
|
+
encoding = 'utf8';
|
|
133
|
+
}
|
|
134
|
+
if (!this._bodyChunks) {
|
|
135
|
+
this._bodyChunks = [];
|
|
136
|
+
}
|
|
137
|
+
this._bodyChunks.push(chunk);
|
|
138
|
+
if (callback) callback();
|
|
139
|
+
return true;
|
|
140
|
+
},
|
|
141
|
+
pipe(stream: any) {
|
|
142
|
+
this.writableEnded = true;
|
|
143
|
+
|
|
144
|
+
// 如果是 ReadableStream,直接使用
|
|
145
|
+
if (stream instanceof ReadableStream) {
|
|
146
|
+
resolve(
|
|
147
|
+
new Response(stream, {
|
|
148
|
+
status: this.statusCode,
|
|
149
|
+
headers: this._headers as any,
|
|
150
|
+
})
|
|
151
|
+
);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 如果是 Node.js 流,转换为 ReadableStream
|
|
156
|
+
const readableStream = new ReadableStream({
|
|
157
|
+
start(controller) {
|
|
158
|
+
stream.on('data', (chunk: any) => {
|
|
159
|
+
controller.enqueue(chunk);
|
|
160
|
+
});
|
|
161
|
+
stream.on('end', () => {
|
|
162
|
+
controller.close();
|
|
163
|
+
});
|
|
164
|
+
stream.on('error', (err: any) => {
|
|
165
|
+
controller.error(err);
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
cancel() {
|
|
169
|
+
if (stream.destroy) {
|
|
170
|
+
stream.destroy();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
resolve(
|
|
176
|
+
new Response(readableStream, {
|
|
177
|
+
status: this.statusCode,
|
|
178
|
+
headers: this._headers as any,
|
|
179
|
+
})
|
|
180
|
+
);
|
|
181
|
+
},
|
|
133
182
|
end(data?: string) {
|
|
134
183
|
this.writableEnded = true;
|
|
184
|
+
|
|
185
|
+
// 合并所有写入的数据块
|
|
186
|
+
let responseData: string | Buffer = data;
|
|
187
|
+
if (this._bodyChunks && this._bodyChunks.length > 0) {
|
|
188
|
+
if (data) this._bodyChunks.push(data);
|
|
189
|
+
// 处理 Buffer 和字符串混合的情况
|
|
190
|
+
const hasBuffer = this._bodyChunks.some(chunk => chunk instanceof Buffer || chunk instanceof Uint8Array);
|
|
191
|
+
if (hasBuffer) {
|
|
192
|
+
// 如果有 Buffer,转换所有内容为 Buffer 后合并
|
|
193
|
+
const buffers = this._bodyChunks.map(chunk => {
|
|
194
|
+
if (chunk instanceof Buffer) return chunk;
|
|
195
|
+
if (chunk instanceof Uint8Array) return Buffer.from(chunk);
|
|
196
|
+
return Buffer.from(String(chunk));
|
|
197
|
+
});
|
|
198
|
+
responseData = Buffer.concat(buffers);
|
|
199
|
+
} else {
|
|
200
|
+
// 纯字符串,直接拼接
|
|
201
|
+
responseData = this._bodyChunks.map(chunk => String(chunk)).join('');
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
135
205
|
resolve(
|
|
136
|
-
new Response(
|
|
206
|
+
new Response(responseData as any, {
|
|
137
207
|
status: this.statusCode,
|
|
138
208
|
headers: this._headers as any,
|
|
139
209
|
})
|
|
@@ -153,7 +223,7 @@ export class BunServer extends ServerBase implements ServerType {
|
|
|
153
223
|
},
|
|
154
224
|
websocket: {
|
|
155
225
|
open: (ws: any) => {
|
|
156
|
-
ws.send('connected');
|
|
226
|
+
ws.send({ type: 'connected' });
|
|
157
227
|
},
|
|
158
228
|
message: async (ws: any, message: string | Buffer) => {
|
|
159
229
|
const pathname = ws.data.pathname || '';
|
|
@@ -163,6 +233,14 @@ export class BunServer extends ServerBase implements ServerType {
|
|
|
163
233
|
},
|
|
164
234
|
close: (ws: any) => {
|
|
165
235
|
// WebSocket 连接关闭
|
|
236
|
+
const id = ws?.data?.id || '';
|
|
237
|
+
if (id) {
|
|
238
|
+
this.emitter.emit(id, { type: 'close', ws, id });
|
|
239
|
+
setTimeout(() => {
|
|
240
|
+
// 关闭后 5 秒清理监听器, 避免内存泄漏, 理论上原本的自己就应该被清理掉了,这里是保险起见
|
|
241
|
+
this.emitter.removeAllListeners(id);
|
|
242
|
+
}, 5000);
|
|
243
|
+
}
|
|
166
244
|
},
|
|
167
245
|
},
|
|
168
246
|
});
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import EventEmitter from 'node:events';
|
|
2
|
+
import * as http from 'node:http';
|
|
3
|
+
|
|
2
4
|
|
|
3
|
-
export type Listener = {
|
|
4
|
-
id?: string;
|
|
5
|
-
io?: boolean;
|
|
6
|
-
path?: string;
|
|
7
|
-
fun: (...args: any[]) => Promise<void> | void;
|
|
8
|
-
}
|
|
9
|
-
export type ListenerFun = (...args: any[]) => Promise<void> | void;
|
|
10
|
-
export type OnListener = Listener | Listener[] | ListenerFun | ListenerFun[];
|
|
11
5
|
export type Cors = {
|
|
12
6
|
/**
|
|
13
7
|
* @default '*''
|
|
@@ -44,27 +38,59 @@ export interface ServerType {
|
|
|
44
38
|
* @param listener
|
|
45
39
|
*/
|
|
46
40
|
on(listener: OnListener): void;
|
|
47
|
-
onWebSocket({ ws, message, pathname, token, id }:
|
|
41
|
+
onWebSocket({ ws, message, pathname, token, id }: OnWebSocketOptions): void;
|
|
48
42
|
}
|
|
49
43
|
|
|
44
|
+
export type OnWebSocketOptions = { ws: WS; message: string | Buffer; pathname: string, token?: string, id?: string }
|
|
45
|
+
export type OnWebSocketFn = (options: OnWebSocketOptions) => Promise<void> | void;
|
|
50
46
|
type WS = {
|
|
51
47
|
send: (data: any) => void;
|
|
52
|
-
close: () => void;
|
|
48
|
+
close: (code?: number, reason?: string) => void;
|
|
49
|
+
}
|
|
50
|
+
export type Listener = {
|
|
51
|
+
id?: string;
|
|
52
|
+
io?: boolean;
|
|
53
|
+
path?: string;
|
|
54
|
+
fun: WebScoketListenerFun | HttpListenerFun;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
export type
|
|
57
|
+
export type WebScoketListenerFun = (req: WebSocketReq, res: WebSocketRes) => Promise<void> | void;
|
|
58
|
+
export type HttpListenerFun = (req: RouterReq, res: RouterRes) => Promise<void> | void;
|
|
59
|
+
|
|
60
|
+
export type WebSocketReq = {
|
|
61
|
+
emitter?: EventEmitter;
|
|
62
|
+
ws: WS;
|
|
63
|
+
data: any;
|
|
64
|
+
pathname?: string;
|
|
65
|
+
token?: string;
|
|
66
|
+
id?: string;
|
|
67
|
+
}
|
|
68
|
+
export type WebSocketRes = {
|
|
69
|
+
end: (data: any) => void;
|
|
70
|
+
}
|
|
71
|
+
export type ListenerFun = WebScoketListenerFun | HttpListenerFun;;
|
|
72
|
+
export type OnListener = Listener | ListenerFun | (Listener | ListenerFun)[];
|
|
73
|
+
export type RouterReq<T = {}> = {
|
|
56
74
|
url: string;
|
|
57
75
|
method: string;
|
|
58
76
|
headers: Record<string, string>;
|
|
59
|
-
|
|
60
|
-
|
|
77
|
+
socket?: {
|
|
78
|
+
remoteAddress?: string;
|
|
79
|
+
remotePort?: number;
|
|
80
|
+
};
|
|
81
|
+
cookies?: Record<string, string>;
|
|
82
|
+
} & T;
|
|
61
83
|
|
|
62
|
-
export type
|
|
84
|
+
export type RouterRes<T = {}> = {
|
|
63
85
|
statusCode: number;
|
|
86
|
+
headersSent: boolean;
|
|
87
|
+
_headers: Record<string, string | string[]>;
|
|
88
|
+
_bodyChunks: any[];
|
|
64
89
|
writableEnded: boolean;
|
|
65
90
|
writeHead: (statusCode: number, headers?: Record<string, string>) => void;
|
|
66
91
|
setHeader: (name: string, value: string | string[]) => void;
|
|
67
92
|
cookie: (name: string, value: string, options?: any) => void;
|
|
93
|
+
write: (chunk: any) => void;
|
|
94
|
+
pipe: (stream: any) => void;
|
|
68
95
|
end: (data?: any) => void;
|
|
69
|
-
|
|
70
|
-
}
|
|
96
|
+
} & T;
|