@kevisual/router 0.0.19 → 0.0.21-beta
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-browser.d.ts +2 -1
- package/dist/router-browser.js +4 -1
- package/dist/router-define.d.ts +2 -1
- package/dist/router-define.js +4 -1
- package/dist/router-simple.d.ts +54 -2
- package/dist/router-simple.js +193 -2
- package/dist/router.d.ts +2 -1
- package/dist/router.js +4 -1
- package/package.json +1 -1
- package/src/router-define.ts +5 -2
- package/src/router-simple.ts +194 -3
package/dist/router-browser.d.ts
CHANGED
|
@@ -495,7 +495,8 @@ declare const util: {
|
|
|
495
495
|
declare class QueryUtil<T extends RouteObject = RouteObject> {
|
|
496
496
|
obj: T;
|
|
497
497
|
app: QueryRouterServer$1;
|
|
498
|
-
|
|
498
|
+
query: Query;
|
|
499
|
+
constructor(object: T, opts?: ChainOptions & QueryChainOptions);
|
|
499
500
|
static createFormObj<U extends RouteObject>(object: U, opts?: ChainOptions): QueryUtil<U>;
|
|
500
501
|
static create<U extends Record<string, RouteOpts$1>>(value: U, opts?: ChainOptions): QueryUtil<U>;
|
|
501
502
|
get<K extends keyof T>(key: K): RouteOpts$1;
|
package/dist/router-browser.js
CHANGED
|
@@ -6427,9 +6427,11 @@ const util = {
|
|
|
6427
6427
|
class QueryUtil {
|
|
6428
6428
|
obj;
|
|
6429
6429
|
app;
|
|
6430
|
+
query;
|
|
6430
6431
|
constructor(object, opts) {
|
|
6431
6432
|
this.obj = object;
|
|
6432
6433
|
this.app = opts?.app;
|
|
6434
|
+
this.query = opts?.query;
|
|
6433
6435
|
}
|
|
6434
6436
|
static createFormObj(object, opts) {
|
|
6435
6437
|
return new QueryUtil(object, opts);
|
|
@@ -6448,7 +6450,8 @@ class QueryUtil {
|
|
|
6448
6450
|
}
|
|
6449
6451
|
queryChain(key, opts) {
|
|
6450
6452
|
const value = this.obj[key];
|
|
6451
|
-
|
|
6453
|
+
let newOpts = { query: this.query, ...opts };
|
|
6454
|
+
return new QueryUtil.QueryChain(value, newOpts);
|
|
6452
6455
|
}
|
|
6453
6456
|
static Chain = Chain;
|
|
6454
6457
|
static QueryChain = QueryChain;
|
package/dist/router-define.d.ts
CHANGED
|
@@ -58,7 +58,8 @@ declare const util: {
|
|
|
58
58
|
declare class QueryUtil<T extends RouteObject = RouteObject> {
|
|
59
59
|
obj: T;
|
|
60
60
|
app: QueryRouterServer;
|
|
61
|
-
|
|
61
|
+
query: Query;
|
|
62
|
+
constructor(object: T, opts?: ChainOptions & QueryChainOptions);
|
|
62
63
|
static createFormObj<U extends RouteObject>(object: U, opts?: ChainOptions): QueryUtil<U>;
|
|
63
64
|
static create<U extends Record<string, RouteOpts>>(value: U, opts?: ChainOptions): QueryUtil<U>;
|
|
64
65
|
get<K extends keyof T>(key: K): RouteOpts;
|
package/dist/router-define.js
CHANGED
|
@@ -97,9 +97,11 @@ const util = {
|
|
|
97
97
|
class QueryUtil {
|
|
98
98
|
obj;
|
|
99
99
|
app;
|
|
100
|
+
query;
|
|
100
101
|
constructor(object, opts) {
|
|
101
102
|
this.obj = object;
|
|
102
103
|
this.app = opts?.app;
|
|
104
|
+
this.query = opts?.query;
|
|
103
105
|
}
|
|
104
106
|
static createFormObj(object, opts) {
|
|
105
107
|
return new QueryUtil(object, opts);
|
|
@@ -118,7 +120,8 @@ class QueryUtil {
|
|
|
118
120
|
}
|
|
119
121
|
queryChain(key, opts) {
|
|
120
122
|
const value = this.obj[key];
|
|
121
|
-
|
|
123
|
+
let newOpts = { query: this.query, ...opts };
|
|
124
|
+
return new QueryUtil.QueryChain(value, newOpts);
|
|
122
125
|
}
|
|
123
126
|
static Chain = Chain;
|
|
124
127
|
static QueryChain = QueryChain;
|
package/dist/router-simple.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import * as querystring from 'querystring';
|
|
2
2
|
import { Key } from 'path-to-regexp';
|
|
3
|
-
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
3
|
+
import { IncomingMessage, ServerResponse, Server } from 'node:http';
|
|
4
|
+
import { ListenOptions } from 'node:net';
|
|
4
5
|
|
|
5
6
|
type Req = IncomingMessage & {
|
|
6
7
|
params?: Record<string, string>;
|
|
7
8
|
};
|
|
9
|
+
type SimpleObject = {
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
};
|
|
8
12
|
interface Route {
|
|
9
13
|
method: string;
|
|
10
14
|
regexp: RegExp;
|
|
@@ -28,7 +32,10 @@ declare class SimpleRouter {
|
|
|
28
32
|
use(method: string, route: string, ...fns: Array<(req: Req, res: ServerResponse) => Promise<void> | void>): this;
|
|
29
33
|
get(route: string, ...fns: Array<(req: Req, res: ServerResponse) => Promise<void> | void>): this;
|
|
30
34
|
post(route: string, ...fns: Array<(req: Req, res: ServerResponse) => Promise<void> | void>): this;
|
|
35
|
+
sse(route: string, ...fns: Array<(req: Req, res: ServerResponse) => Promise<void> | void>): this;
|
|
31
36
|
all(route: string, ...fns: Array<(req: Req, res: ServerResponse) => Promise<void> | void>): this;
|
|
37
|
+
getJson(v: string | number | boolean | SimpleObject): any;
|
|
38
|
+
isSse(req: Req): boolean;
|
|
32
39
|
/**
|
|
33
40
|
* 解析 req 和 res 请求
|
|
34
41
|
* @param req
|
|
@@ -36,6 +43,51 @@ declare class SimpleRouter {
|
|
|
36
43
|
* @returns
|
|
37
44
|
*/
|
|
38
45
|
parse(req: Req, res: ServerResponse): Promise<void> | "is_exclude" | "not_found";
|
|
46
|
+
/**
|
|
47
|
+
* 创建一个新的 HttpChain 实例
|
|
48
|
+
* @param req
|
|
49
|
+
* @param res
|
|
50
|
+
* @returns
|
|
51
|
+
*/
|
|
52
|
+
chain(req?: Req, res?: ServerResponse): HttpChain;
|
|
53
|
+
static Chain(opts?: HttpChainOpts): HttpChain;
|
|
54
|
+
}
|
|
55
|
+
type HttpChainOpts = {
|
|
56
|
+
req?: Req;
|
|
57
|
+
res?: ServerResponse;
|
|
58
|
+
simpleRouter?: SimpleRouter;
|
|
59
|
+
};
|
|
60
|
+
declare class HttpChain {
|
|
61
|
+
req: Req;
|
|
62
|
+
res: ServerResponse;
|
|
63
|
+
simpleRouter: SimpleRouter;
|
|
64
|
+
server: Server;
|
|
65
|
+
hasSetHeader: boolean;
|
|
66
|
+
isSseSet: boolean;
|
|
67
|
+
constructor(opts?: HttpChainOpts);
|
|
68
|
+
setReq(req: Req): this;
|
|
69
|
+
setRes(res: ServerResponse): this;
|
|
70
|
+
setRouter(router: SimpleRouter): this;
|
|
71
|
+
setServer(server: Server): this;
|
|
72
|
+
/**
|
|
73
|
+
* 兼容 express 的一点功能
|
|
74
|
+
* @param status
|
|
75
|
+
* @returns
|
|
76
|
+
*/
|
|
77
|
+
status(status: number): this;
|
|
78
|
+
writeHead(status: number): this;
|
|
79
|
+
json(data: SimpleObject): this;
|
|
80
|
+
/**
|
|
81
|
+
* 兼容 express 的一点功能
|
|
82
|
+
* @param data
|
|
83
|
+
* @returns
|
|
84
|
+
*/
|
|
85
|
+
end(data: SimpleObject | string): this;
|
|
86
|
+
listen(opts: ListenOptions, callback?: () => void): this;
|
|
87
|
+
parse(): () => void;
|
|
88
|
+
getString(value: string | SimpleObject): string;
|
|
89
|
+
sse(value: string | SimpleObject): this;
|
|
90
|
+
close(): this;
|
|
39
91
|
}
|
|
40
92
|
|
|
41
|
-
export { SimpleRouter };
|
|
93
|
+
export { HttpChain, SimpleRouter };
|
package/dist/router-simple.js
CHANGED
|
@@ -511,11 +511,36 @@ class SimpleRouter {
|
|
|
511
511
|
post(route, ...fns) {
|
|
512
512
|
return this.use('post', route, ...fns);
|
|
513
513
|
}
|
|
514
|
+
sse(route, ...fns) {
|
|
515
|
+
return this.use('sse', route, ...fns);
|
|
516
|
+
}
|
|
514
517
|
all(route, ...fns) {
|
|
515
518
|
this.use('post', route, ...fns);
|
|
516
519
|
this.use('get', route, ...fns);
|
|
520
|
+
this.use('sse', route, ...fns);
|
|
517
521
|
return this;
|
|
518
522
|
}
|
|
523
|
+
getJson(v) {
|
|
524
|
+
if (typeof v === 'object') {
|
|
525
|
+
return v;
|
|
526
|
+
}
|
|
527
|
+
try {
|
|
528
|
+
return JSON.parse(v);
|
|
529
|
+
}
|
|
530
|
+
catch (e) {
|
|
531
|
+
return {};
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
isSse(req) {
|
|
535
|
+
const { headers } = req;
|
|
536
|
+
if (headers['accept'] && headers['accept'].includes('text/event-stream')) {
|
|
537
|
+
return true;
|
|
538
|
+
}
|
|
539
|
+
if (headers['content-type'] && headers['content-type'].includes('text/event-stream')) {
|
|
540
|
+
return true;
|
|
541
|
+
}
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
519
544
|
/**
|
|
520
545
|
* 解析 req 和 res 请求
|
|
521
546
|
* @param req
|
|
@@ -524,10 +549,13 @@ class SimpleRouter {
|
|
|
524
549
|
*/
|
|
525
550
|
parse(req, res) {
|
|
526
551
|
const { pathname } = new URL(req.url, 'http://localhost');
|
|
527
|
-
|
|
552
|
+
let method = req.method.toLowerCase();
|
|
528
553
|
if (this.exclude.includes(pathname)) {
|
|
529
554
|
return 'is_exclude';
|
|
530
555
|
}
|
|
556
|
+
const isSse = this.isSse(req);
|
|
557
|
+
if (isSse)
|
|
558
|
+
method = 'sse';
|
|
531
559
|
const route = this.routes.find((route) => {
|
|
532
560
|
const matchResult = route.regexp.exec(pathname);
|
|
533
561
|
if (matchResult && route.method === method) {
|
|
@@ -545,6 +573,169 @@ class SimpleRouter {
|
|
|
545
573
|
}
|
|
546
574
|
return 'not_found';
|
|
547
575
|
}
|
|
576
|
+
/**
|
|
577
|
+
* 创建一个新的 HttpChain 实例
|
|
578
|
+
* @param req
|
|
579
|
+
* @param res
|
|
580
|
+
* @returns
|
|
581
|
+
*/
|
|
582
|
+
chain(req, res) {
|
|
583
|
+
const chain = new HttpChain({ req, res, simpleRouter: this });
|
|
584
|
+
return chain;
|
|
585
|
+
}
|
|
586
|
+
static Chain(opts) {
|
|
587
|
+
return new HttpChain(opts);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
class HttpChain {
|
|
591
|
+
req;
|
|
592
|
+
res;
|
|
593
|
+
simpleRouter;
|
|
594
|
+
server;
|
|
595
|
+
hasSetHeader = false;
|
|
596
|
+
isSseSet = false;
|
|
597
|
+
constructor(opts) {
|
|
598
|
+
this.req = opts?.req;
|
|
599
|
+
this.res = opts?.res;
|
|
600
|
+
this.simpleRouter = opts?.simpleRouter;
|
|
601
|
+
}
|
|
602
|
+
setReq(req) {
|
|
603
|
+
this.req = req;
|
|
604
|
+
return this;
|
|
605
|
+
}
|
|
606
|
+
setRes(res) {
|
|
607
|
+
this.res = res;
|
|
608
|
+
return this;
|
|
609
|
+
}
|
|
610
|
+
setRouter(router) {
|
|
611
|
+
this.simpleRouter = router;
|
|
612
|
+
return this;
|
|
613
|
+
}
|
|
614
|
+
setServer(server) {
|
|
615
|
+
this.server = server;
|
|
616
|
+
return this;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* 兼容 express 的一点功能
|
|
620
|
+
* @param status
|
|
621
|
+
* @returns
|
|
622
|
+
*/
|
|
623
|
+
status(status) {
|
|
624
|
+
if (!this.res)
|
|
625
|
+
return this;
|
|
626
|
+
if (this.hasSetHeader) {
|
|
627
|
+
return this;
|
|
628
|
+
}
|
|
629
|
+
this.hasSetHeader = true;
|
|
630
|
+
this.res.writeHead(status);
|
|
631
|
+
return this;
|
|
632
|
+
}
|
|
633
|
+
writeHead(status) {
|
|
634
|
+
if (!this.res)
|
|
635
|
+
return this;
|
|
636
|
+
if (this.hasSetHeader) {
|
|
637
|
+
return this;
|
|
638
|
+
}
|
|
639
|
+
this.hasSetHeader = true;
|
|
640
|
+
this.res.writeHead(status);
|
|
641
|
+
return this;
|
|
642
|
+
}
|
|
643
|
+
json(data) {
|
|
644
|
+
if (!this.res)
|
|
645
|
+
return this;
|
|
646
|
+
this.res.end(JSON.stringify(data));
|
|
647
|
+
return this;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* 兼容 express 的一点功能
|
|
651
|
+
* @param data
|
|
652
|
+
* @returns
|
|
653
|
+
*/
|
|
654
|
+
end(data) {
|
|
655
|
+
if (!this.res)
|
|
656
|
+
return this;
|
|
657
|
+
if (typeof data === 'object') {
|
|
658
|
+
this.res.end(JSON.stringify(data));
|
|
659
|
+
}
|
|
660
|
+
else if (typeof data === 'string') {
|
|
661
|
+
this.res.end(data);
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
this.res.end('nothing');
|
|
665
|
+
}
|
|
666
|
+
return this;
|
|
667
|
+
}
|
|
668
|
+
listen(opts, callback) {
|
|
669
|
+
this.server.listen(opts, callback);
|
|
670
|
+
return this;
|
|
671
|
+
}
|
|
672
|
+
parse() {
|
|
673
|
+
if (!this.server || !this.simpleRouter) {
|
|
674
|
+
throw new Error('Server and SimpleRouter must be set before calling parse');
|
|
675
|
+
}
|
|
676
|
+
const that = this;
|
|
677
|
+
const listener = (req, res) => {
|
|
678
|
+
try {
|
|
679
|
+
that.simpleRouter.parse(req, res);
|
|
680
|
+
}
|
|
681
|
+
catch (error) {
|
|
682
|
+
console.error('Error parsing request:', error);
|
|
683
|
+
if (!res.headersSent) {
|
|
684
|
+
res.writeHead(500);
|
|
685
|
+
res.end(JSON.stringify({ code: 500, message: 'Internal Server Error' }));
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
this.server.on('request', listener);
|
|
690
|
+
return () => {
|
|
691
|
+
that.server.removeListener('request', listener);
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
getString(value) {
|
|
695
|
+
if (typeof value === 'string') {
|
|
696
|
+
return value;
|
|
697
|
+
}
|
|
698
|
+
return JSON.stringify(value);
|
|
699
|
+
}
|
|
700
|
+
sse(value) {
|
|
701
|
+
const res = this.res;
|
|
702
|
+
const req = this.req;
|
|
703
|
+
if (!res || !req)
|
|
704
|
+
return;
|
|
705
|
+
const data = this.getString(value);
|
|
706
|
+
if (this.isSseSet) {
|
|
707
|
+
res.write(`data: ${data}\n\n`);
|
|
708
|
+
return this;
|
|
709
|
+
}
|
|
710
|
+
const headersMap = new Map([
|
|
711
|
+
['Content-Type', 'text/event-stream'],
|
|
712
|
+
['Cache-Control', 'no-cache'],
|
|
713
|
+
['Connection', 'keep-alive'],
|
|
714
|
+
]);
|
|
715
|
+
this.isSseSet = true;
|
|
716
|
+
let intervalId;
|
|
717
|
+
if (!this.hasSetHeader) {
|
|
718
|
+
this.hasSetHeader = true;
|
|
719
|
+
res.setHeaders(headersMap);
|
|
720
|
+
// 每隔 2 秒发送一个空行,保持连接
|
|
721
|
+
setInterval(() => {
|
|
722
|
+
res.write('\n'); // 发送一个空行,保持连接
|
|
723
|
+
}, 3000);
|
|
724
|
+
// 客户端断开连接时清理
|
|
725
|
+
req.on('close', () => {
|
|
726
|
+
clearInterval(intervalId);
|
|
727
|
+
res.end();
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
this.res.write(`data: ${data}\n\n`);
|
|
731
|
+
return this;
|
|
732
|
+
}
|
|
733
|
+
close() {
|
|
734
|
+
if (this.req?.destroy) {
|
|
735
|
+
this.req.destroy();
|
|
736
|
+
}
|
|
737
|
+
return this;
|
|
738
|
+
}
|
|
548
739
|
}
|
|
549
740
|
|
|
550
|
-
export { SimpleRouter };
|
|
741
|
+
export { HttpChain, SimpleRouter };
|
package/dist/router.d.ts
CHANGED
|
@@ -636,7 +636,8 @@ declare const util: {
|
|
|
636
636
|
declare class QueryUtil<T extends RouteObject = RouteObject> {
|
|
637
637
|
obj: T;
|
|
638
638
|
app: QueryRouterServer$1;
|
|
639
|
-
|
|
639
|
+
query: Query;
|
|
640
|
+
constructor(object: T, opts?: ChainOptions & QueryChainOptions);
|
|
640
641
|
static createFormObj<U extends RouteObject>(object: U, opts?: ChainOptions): QueryUtil<U>;
|
|
641
642
|
static create<U extends Record<string, RouteOpts$1>>(value: U, opts?: ChainOptions): QueryUtil<U>;
|
|
642
643
|
get<K extends keyof T>(key: K): RouteOpts$1;
|
package/dist/router.js
CHANGED
|
@@ -12066,9 +12066,11 @@ const util = {
|
|
|
12066
12066
|
class QueryUtil {
|
|
12067
12067
|
obj;
|
|
12068
12068
|
app;
|
|
12069
|
+
query;
|
|
12069
12070
|
constructor(object, opts) {
|
|
12070
12071
|
this.obj = object;
|
|
12071
12072
|
this.app = opts?.app;
|
|
12073
|
+
this.query = opts?.query;
|
|
12072
12074
|
}
|
|
12073
12075
|
static createFormObj(object, opts) {
|
|
12074
12076
|
return new QueryUtil(object, opts);
|
|
@@ -12087,7 +12089,8 @@ class QueryUtil {
|
|
|
12087
12089
|
}
|
|
12088
12090
|
queryChain(key, opts) {
|
|
12089
12091
|
const value = this.obj[key];
|
|
12090
|
-
|
|
12092
|
+
let newOpts = { query: this.query, ...opts };
|
|
12093
|
+
return new QueryUtil.QueryChain(value, newOpts);
|
|
12091
12094
|
}
|
|
12092
12095
|
static Chain = Chain;
|
|
12093
12096
|
static QueryChain = QueryChain;
|
package/package.json
CHANGED
package/src/router-define.ts
CHANGED
|
@@ -120,9 +120,11 @@ export const util = {
|
|
|
120
120
|
export class QueryUtil<T extends RouteObject = RouteObject> {
|
|
121
121
|
obj: T;
|
|
122
122
|
app: QueryRouterServer;
|
|
123
|
-
|
|
123
|
+
query: Query;
|
|
124
|
+
constructor(object: T, opts?: ChainOptions & QueryChainOptions) {
|
|
124
125
|
this.obj = object;
|
|
125
126
|
this.app = opts?.app;
|
|
127
|
+
this.query = opts?.query;
|
|
126
128
|
}
|
|
127
129
|
static createFormObj<U extends RouteObject>(object: U, opts?: ChainOptions) {
|
|
128
130
|
return new QueryUtil<U>(object, opts);
|
|
@@ -141,7 +143,8 @@ export class QueryUtil<T extends RouteObject = RouteObject> {
|
|
|
141
143
|
}
|
|
142
144
|
queryChain<K extends keyof T>(key: K, opts?: QueryChainOptions) {
|
|
143
145
|
const value = this.obj[key];
|
|
144
|
-
|
|
146
|
+
let newOpts = { query: this.query, ...opts };
|
|
147
|
+
return new QueryUtil.QueryChain(value, newOpts);
|
|
145
148
|
}
|
|
146
149
|
static Chain = Chain;
|
|
147
150
|
static QueryChain = QueryChain;
|
package/src/router-simple.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { pathToRegexp, Key } from 'path-to-regexp';
|
|
2
|
-
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { IncomingMessage, ServerResponse, Server } from 'node:http';
|
|
3
3
|
import { parseBody, parseSearch, parseSearchValue } from './server/parse-body.ts';
|
|
4
|
+
import { ListenOptions } from 'node:net';
|
|
4
5
|
|
|
5
6
|
type Req = IncomingMessage & { params?: Record<string, string> };
|
|
7
|
+
type SimpleObject = {
|
|
8
|
+
[key: string]: any;
|
|
9
|
+
};
|
|
6
10
|
interface Route {
|
|
7
11
|
method: string;
|
|
8
12
|
regexp: RegExp;
|
|
@@ -28,7 +32,6 @@ export class SimpleRouter {
|
|
|
28
32
|
use(method: string, route: string, ...fns: Array<(req: Req, res: ServerResponse) => Promise<void> | void>) {
|
|
29
33
|
const handlers = Array.isArray(fns) ? fns.flat() : [];
|
|
30
34
|
const pattern = pathToRegexp(route);
|
|
31
|
-
|
|
32
35
|
this.routes.push({ method: method.toLowerCase(), regexp: pattern.regexp, keys: pattern.keys, handlers });
|
|
33
36
|
return this;
|
|
34
37
|
}
|
|
@@ -38,11 +41,35 @@ export class SimpleRouter {
|
|
|
38
41
|
post(route: string, ...fns: Array<(req: Req, res: ServerResponse) => Promise<void> | void>) {
|
|
39
42
|
return this.use('post', route, ...fns);
|
|
40
43
|
}
|
|
44
|
+
sse(route: string, ...fns: Array<(req: Req, res: ServerResponse) => Promise<void> | void>) {
|
|
45
|
+
return this.use('sse', route, ...fns);
|
|
46
|
+
}
|
|
41
47
|
all(route: string, ...fns: Array<(req: Req, res: ServerResponse) => Promise<void> | void>) {
|
|
42
48
|
this.use('post', route, ...fns);
|
|
43
49
|
this.use('get', route, ...fns);
|
|
50
|
+
this.use('sse', route, ...fns);
|
|
44
51
|
return this;
|
|
45
52
|
}
|
|
53
|
+
getJson(v: string | number | boolean | SimpleObject) {
|
|
54
|
+
if (typeof v === 'object') {
|
|
55
|
+
return v;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
return JSON.parse(v as string);
|
|
59
|
+
} catch (e) {
|
|
60
|
+
return {};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
isSse(req: Req) {
|
|
64
|
+
const { headers } = req;
|
|
65
|
+
if (headers['accept'] && headers['accept'].includes('text/event-stream')) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
if (headers['content-type'] && headers['content-type'].includes('text/event-stream')) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
46
73
|
/**
|
|
47
74
|
* 解析 req 和 res 请求
|
|
48
75
|
* @param req
|
|
@@ -51,10 +78,12 @@ export class SimpleRouter {
|
|
|
51
78
|
*/
|
|
52
79
|
parse(req: Req, res: ServerResponse) {
|
|
53
80
|
const { pathname } = new URL(req.url, 'http://localhost');
|
|
54
|
-
|
|
81
|
+
let method = req.method.toLowerCase();
|
|
55
82
|
if (this.exclude.includes(pathname)) {
|
|
56
83
|
return 'is_exclude';
|
|
57
84
|
}
|
|
85
|
+
const isSse = this.isSse(req);
|
|
86
|
+
if (isSse) method = 'sse';
|
|
58
87
|
const route = this.routes.find((route) => {
|
|
59
88
|
const matchResult = route.regexp.exec(pathname);
|
|
60
89
|
if (matchResult && route.method === method) {
|
|
@@ -74,4 +103,166 @@ export class SimpleRouter {
|
|
|
74
103
|
|
|
75
104
|
return 'not_found';
|
|
76
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* 创建一个新的 HttpChain 实例
|
|
108
|
+
* @param req
|
|
109
|
+
* @param res
|
|
110
|
+
* @returns
|
|
111
|
+
*/
|
|
112
|
+
chain(req?: Req, res?: ServerResponse) {
|
|
113
|
+
const chain = new HttpChain({ req, res, simpleRouter: this });
|
|
114
|
+
return chain;
|
|
115
|
+
}
|
|
116
|
+
static Chain(opts?: HttpChainOpts) {
|
|
117
|
+
return new HttpChain(opts);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
type HttpChainOpts = {
|
|
122
|
+
req?: Req;
|
|
123
|
+
res?: ServerResponse;
|
|
124
|
+
simpleRouter?: SimpleRouter;
|
|
125
|
+
};
|
|
126
|
+
export class HttpChain {
|
|
127
|
+
req: Req;
|
|
128
|
+
res: ServerResponse;
|
|
129
|
+
simpleRouter: SimpleRouter;
|
|
130
|
+
server: Server;
|
|
131
|
+
hasSetHeader: boolean = false;
|
|
132
|
+
isSseSet: boolean = false;
|
|
133
|
+
constructor(opts?: HttpChainOpts) {
|
|
134
|
+
this.req = opts?.req;
|
|
135
|
+
this.res = opts?.res;
|
|
136
|
+
this.simpleRouter = opts?.simpleRouter;
|
|
137
|
+
}
|
|
138
|
+
setReq(req: Req) {
|
|
139
|
+
this.req = req;
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
setRes(res: ServerResponse) {
|
|
143
|
+
this.res = res;
|
|
144
|
+
return this;
|
|
145
|
+
}
|
|
146
|
+
setRouter(router: SimpleRouter) {
|
|
147
|
+
this.simpleRouter = router;
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
setServer(server: Server) {
|
|
151
|
+
this.server = server;
|
|
152
|
+
return this;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* 兼容 express 的一点功能
|
|
156
|
+
* @param status
|
|
157
|
+
* @returns
|
|
158
|
+
*/
|
|
159
|
+
status(status: number) {
|
|
160
|
+
if (!this.res) return this;
|
|
161
|
+
if (this.hasSetHeader) {
|
|
162
|
+
return this;
|
|
163
|
+
}
|
|
164
|
+
this.hasSetHeader = true;
|
|
165
|
+
this.res.writeHead(status);
|
|
166
|
+
return this;
|
|
167
|
+
}
|
|
168
|
+
writeHead(status: number) {
|
|
169
|
+
if (!this.res) return this;
|
|
170
|
+
if (this.hasSetHeader) {
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
this.hasSetHeader = true;
|
|
174
|
+
this.res.writeHead(status);
|
|
175
|
+
return this;
|
|
176
|
+
}
|
|
177
|
+
json(data: SimpleObject) {
|
|
178
|
+
if (!this.res) return this;
|
|
179
|
+
this.res.end(JSON.stringify(data));
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* 兼容 express 的一点功能
|
|
184
|
+
* @param data
|
|
185
|
+
* @returns
|
|
186
|
+
*/
|
|
187
|
+
end(data: SimpleObject | string) {
|
|
188
|
+
if (!this.res) return this;
|
|
189
|
+
if (typeof data === 'object') {
|
|
190
|
+
this.res.end(JSON.stringify(data));
|
|
191
|
+
} else if (typeof data === 'string') {
|
|
192
|
+
this.res.end(data);
|
|
193
|
+
} else {
|
|
194
|
+
this.res.end('nothing');
|
|
195
|
+
}
|
|
196
|
+
return this;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
listen(opts: ListenOptions, callback?: () => void) {
|
|
200
|
+
this.server.listen(opts, callback);
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
parse() {
|
|
204
|
+
if (!this.server || !this.simpleRouter) {
|
|
205
|
+
throw new Error('Server and SimpleRouter must be set before calling parse');
|
|
206
|
+
}
|
|
207
|
+
const that = this;
|
|
208
|
+
const listener = (req: Req, res: ServerResponse) => {
|
|
209
|
+
try {
|
|
210
|
+
that.simpleRouter.parse(req, res);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error('Error parsing request:', error);
|
|
213
|
+
if (!res.headersSent) {
|
|
214
|
+
res.writeHead(500);
|
|
215
|
+
res.end(JSON.stringify({ code: 500, message: 'Internal Server Error' }));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
this.server.on('request', listener);
|
|
220
|
+
return () => {
|
|
221
|
+
that.server.removeListener('request', listener);
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
getString(value: string | SimpleObject) {
|
|
225
|
+
if (typeof value === 'string') {
|
|
226
|
+
return value;
|
|
227
|
+
}
|
|
228
|
+
return JSON.stringify(value);
|
|
229
|
+
}
|
|
230
|
+
sse(value: string | SimpleObject) {
|
|
231
|
+
const res = this.res;
|
|
232
|
+
const req = this.req;
|
|
233
|
+
if (!res || !req) return;
|
|
234
|
+
const data = this.getString(value);
|
|
235
|
+
if (this.isSseSet) {
|
|
236
|
+
res.write(`data: ${data}\n\n`);
|
|
237
|
+
return this;
|
|
238
|
+
}
|
|
239
|
+
const headersMap = new Map<string, string>([
|
|
240
|
+
['Content-Type', 'text/event-stream'],
|
|
241
|
+
['Cache-Control', 'no-cache'],
|
|
242
|
+
['Connection', 'keep-alive'],
|
|
243
|
+
]);
|
|
244
|
+
this.isSseSet = true;
|
|
245
|
+
let intervalId: NodeJS.Timeout;
|
|
246
|
+
if (!this.hasSetHeader) {
|
|
247
|
+
this.hasSetHeader = true;
|
|
248
|
+
res.setHeaders(headersMap);
|
|
249
|
+
// 每隔 2 秒发送一个空行,保持连接
|
|
250
|
+
setInterval(() => {
|
|
251
|
+
res.write('\n'); // 发送一个空行,保持连接
|
|
252
|
+
}, 3000);
|
|
253
|
+
// 客户端断开连接时清理
|
|
254
|
+
req.on('close', () => {
|
|
255
|
+
clearInterval(intervalId);
|
|
256
|
+
res.end();
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
this.res.write(`data: ${data}\n\n`);
|
|
260
|
+
return this;
|
|
261
|
+
}
|
|
262
|
+
close() {
|
|
263
|
+
if (this.req?.destroy) {
|
|
264
|
+
this.req.destroy();
|
|
265
|
+
}
|
|
266
|
+
return this;
|
|
267
|
+
}
|
|
77
268
|
}
|