@dooboostore/simple-boot-http-server 1.0.26 → 1.0.27
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 +100 -14
- package/dist/cjs/SimpleBootHttpServer.js +20 -16
- package/dist/cjs/SimpleBootHttpServer.js.map +2 -2
- package/dist/cjs/codes/Mimes.js +2 -0
- package/dist/cjs/codes/Mimes.js.map +2 -2
- package/dist/cjs/decorators/MethodMapping.js +2 -2
- package/dist/cjs/decorators/MethodMapping.js.map +2 -2
- package/dist/cjs/filters/ResourceFilter.js +0 -1
- package/dist/cjs/filters/ResourceFilter.js.map +2 -2
- package/dist/cjs/websocket/WebSocketManager.js +68 -40
- package/dist/cjs/websocket/WebSocketManager.js.map +2 -2
- package/dist/esm/SimpleBootHttpServer.js +20 -16
- package/dist/esm/SimpleBootHttpServer.js.map +2 -2
- package/dist/esm/codes/Mimes.js +2 -0
- package/dist/esm/codes/Mimes.js.map +2 -2
- package/dist/esm/decorators/MethodMapping.js +2 -2
- package/dist/esm/decorators/MethodMapping.js.map +2 -2
- package/dist/esm/filters/ResourceFilter.js +0 -1
- package/dist/esm/filters/ResourceFilter.js.map +2 -2
- package/dist/esm/websocket/WebSocketManager.js +68 -40
- package/dist/esm/websocket/WebSocketManager.js.map +2 -2
- package/dist/esm-bundle/dooboostore-simple-boot-http-server.esm.js +279 -137
- package/dist/esm-bundle/dooboostore-simple-boot-http-server.esm.js.map +4 -4
- package/dist/types/SimpleBootHttpServer.d.ts.map +1 -1
- package/dist/types/codes/Mimes.d.ts +2 -0
- package/dist/types/codes/Mimes.d.ts.map +1 -1
- package/dist/types/decorators/MethodMapping.d.ts +8 -0
- package/dist/types/decorators/MethodMapping.d.ts.map +1 -1
- package/dist/types/filters/ResourceFilter.d.ts.map +1 -1
- package/dist/types/websocket/WebSocketManager.d.ts +3 -1
- package/dist/types/websocket/WebSocketManager.d.ts.map +1 -1
- package/dist/umd-bundle/dooboostore-simple-boot-http-server.umd.js +279 -137
- package/dist/umd-bundle/dooboostore-simple-boot-http-server.umd.js.map +4 -4
- package/dist/umd-bundle/websocket-client.umd.js +209 -9
- package/dist/umd-bundle/websocket-client.umd.js.map +4 -4
- package/package.json +8 -8
- package/src/SimpleBootHttpServer.ts +21 -16
- package/src/codes/Mimes.ts +2 -0
- package/src/decorators/MethodMapping.ts +14 -7
- package/src/filters/ResourceFilter.ts +0 -1
- package/src/websocket/WebSocketManager.ts +69 -41
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dooboostore/simple-boot-http-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.27",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -67,12 +67,7 @@
|
|
|
67
67
|
"url": "https://github.com/dooboostore-develop/packages"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"reflect-metadata": "^0.2.2"
|
|
71
|
-
"@dooboostore/core": "1.0.32",
|
|
72
|
-
"@dooboostore/core-node": "1.0.13",
|
|
73
|
-
"@dooboostore/core-web": "1.0.13",
|
|
74
|
-
"@dooboostore/dom-render": "1.0.114",
|
|
75
|
-
"@dooboostore/simple-boot": "1.0.57"
|
|
70
|
+
"reflect-metadata": "^0.2.2"
|
|
76
71
|
},
|
|
77
72
|
"devDependencies": {
|
|
78
73
|
"@babel/core": "^7.24.0",
|
|
@@ -102,7 +97,12 @@
|
|
|
102
97
|
"fast-json-patch": "^3.1.0",
|
|
103
98
|
"mime-types": "^2.1.34",
|
|
104
99
|
"node-gzip": "^1.1.2",
|
|
105
|
-
"ws": "^8.18.3"
|
|
100
|
+
"ws": "^8.18.3",
|
|
101
|
+
"@dooboostore/core": "1.0.32",
|
|
102
|
+
"@dooboostore/core-node": "1.0.13",
|
|
103
|
+
"@dooboostore/core-web": "1.0.13",
|
|
104
|
+
"@dooboostore/simple-boot": "1.0.57",
|
|
105
|
+
"@dooboostore/dom-render": "1.0.114"
|
|
106
106
|
},
|
|
107
107
|
"scripts": {
|
|
108
108
|
"clean": "rm -rf dist",
|
|
@@ -326,23 +326,28 @@ export class SimpleBootHttpServer extends SimpleApplication {
|
|
|
326
326
|
여기에서 정해진 타입 리턴에 따른 조치
|
|
327
327
|
...
|
|
328
328
|
*/
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
329
|
+
const resConfig = it.config?.res;
|
|
330
|
+
if (resConfig?.manual === true) {
|
|
331
|
+
// 핸들러가 ServerResponse를 직접 주입받아 스스로 write/end를 제어한다 (예: SSE 스트리밍)
|
|
332
|
+
} else {
|
|
333
|
+
if (it.config?.resolver) {
|
|
334
|
+
const execute = typeof it.config.resolver === 'function' ? this.simstanceManager.getOrNewSim({target: it.config.resolver}) : it.config.resolver;
|
|
335
|
+
data = await execute?.resolve?.(data, rr);
|
|
336
|
+
}
|
|
337
|
+
const status = resConfig?.status ?? HttpStatus.Ok;
|
|
338
|
+
const headers = resConfig?.header ?? {};
|
|
339
|
+
if (resConfig?.contentType) {
|
|
340
|
+
headers[HttpHeaders.ContentType] = resConfig.contentType;
|
|
341
|
+
}
|
|
342
|
+
if ((resConfig?.contentType?.toLowerCase().indexOf(Mimes.ApplicationJson.toLowerCase()) ?? -1) > -1) {
|
|
343
|
+
data = JSON.stringify(data);
|
|
344
|
+
} else if (data && typeof data === 'object') {
|
|
345
|
+
data = JSON.stringify(data);
|
|
346
|
+
}
|
|
347
|
+
rr.resSetHeaders(headers)
|
|
348
|
+
rr.resSetStatusCode(status);
|
|
349
|
+
rr.resWrite(data);
|
|
342
350
|
}
|
|
343
|
-
rr.resSetHeaders(headers)
|
|
344
|
-
rr.resSetStatusCode(status);
|
|
345
|
-
rr.resWrite(data);
|
|
346
351
|
}
|
|
347
352
|
}
|
|
348
353
|
|
package/src/codes/Mimes.ts
CHANGED
|
@@ -24,6 +24,8 @@ export enum Mimes {
|
|
|
24
24
|
ImageXpixmap = 'image/x-xpixmap',
|
|
25
25
|
ImageXxbm = 'image/x-xbm',
|
|
26
26
|
MultipartFormData = 'multipart/form-data',
|
|
27
|
+
TextEventStream = 'text/event-stream', // SSE 실시간 텍스트 스트리밍
|
|
28
|
+
ApplicationXndjson = 'application/x-ndjson',
|
|
27
29
|
ApplicationJsonPostSimpleBootSsrIntentScheme = 'application/json-post+simple-boot-ssr-intent-scheme',
|
|
28
30
|
Intent = 'intent'
|
|
29
31
|
}
|
|
@@ -24,11 +24,18 @@ export type MappingConfig = {
|
|
|
24
24
|
accept?: (Mimes|string)[];
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
res?:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
res?:
|
|
28
|
+
| {
|
|
29
|
+
status?: number;
|
|
30
|
+
header?: {[key: string]: string};
|
|
31
|
+
contentType?: Mimes | string;
|
|
32
|
+
manual?: false;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* manual: true — 리턴값을 이용한 자동 status/header/body 처리(및 resolver 호출)를 건너뛴다.
|
|
36
|
+
* SSE 등 핸들러가 ServerResponse를 직접 주입받아 writeHead/write/end를 스스로 제어하는 경우 사용.
|
|
37
|
+
*/
|
|
38
|
+
| { manual: true };
|
|
32
39
|
resolver?: Resolver|ConstructorType<Resolver>;
|
|
33
40
|
}
|
|
34
41
|
export type SaveMappingConfig = {propertyKey: string | symbol; config: MappingConfig}
|
|
@@ -156,7 +163,7 @@ export const getPATCH = (target: any, propertyKey: string | symbol): MappingConf
|
|
|
156
163
|
return getUrlMapping(target, propertyKey);
|
|
157
164
|
}
|
|
158
165
|
export const getPATCHS = (target: any): SaveMappingConfig[] => {
|
|
159
|
-
return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.
|
|
166
|
+
return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.PATCH);
|
|
160
167
|
}
|
|
161
168
|
export function OPTIONS(config: Omit<MappingConfig, 'method'>): ReflectMethod;
|
|
162
169
|
export function OPTIONS(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
|
|
@@ -194,7 +201,7 @@ export const getHEAD = (target: any, propertyKey: string | symbol): MappingConfi
|
|
|
194
201
|
return getUrlMapping(target, propertyKey);
|
|
195
202
|
}
|
|
196
203
|
export const getHEADS = (target: any): SaveMappingConfig[] => {
|
|
197
|
-
return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.
|
|
204
|
+
return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.HEAD);
|
|
198
205
|
}
|
|
199
206
|
export function TRACE(config: Omit<MappingConfig, 'method'>): ReflectMethod;
|
|
200
207
|
export function TRACE(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
|
|
@@ -90,7 +90,9 @@ type RegisteredClientSet<T = any> = RegisteredClient<T> & { webSocketSet: WebSoc
|
|
|
90
90
|
|
|
91
91
|
@Sim
|
|
92
92
|
export class WebSocketManager implements WebSocketEndPoint {
|
|
93
|
-
private
|
|
93
|
+
private connections: Map<WebSocketSet, Set<string>> = new Map();
|
|
94
|
+
private registrations: Map<string, RegisteredClientSet> = new Map();
|
|
95
|
+
private targets: Map<string, Set<string>> = new Map();
|
|
94
96
|
private eventSubscriptions: Map<string, Map<string, EventResponseCallBack>> = new Map();
|
|
95
97
|
private subject = new Subject<TopicRequest | TopicUnsubscribeRequest | TopicSubscribeRequest | TopicServerEventResponse>();
|
|
96
98
|
constructor(
|
|
@@ -102,67 +104,82 @@ export class WebSocketManager implements WebSocketEndPoint {
|
|
|
102
104
|
return this.subject.asObservable();
|
|
103
105
|
}
|
|
104
106
|
connect(wsSet: WebSocketSet) {
|
|
105
|
-
this.
|
|
107
|
+
this.connections.set(wsSet, new Set<string>());
|
|
106
108
|
console.log('WebSocket connected:', wsSet.request.url);
|
|
107
109
|
}
|
|
108
110
|
close(wsSet: WebSocketSet) {
|
|
109
111
|
this.removeAllClientsByWebSocketSet(wsSet);
|
|
110
|
-
this.
|
|
112
|
+
this.connections.delete(wsSet);
|
|
111
113
|
console.log('WebSocket disconnected:', wsSet.request.url);
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
setClientTopic(wsSet: WebSocketSet, uuid: string, target: string, subscribeBody?: any) {
|
|
115
|
-
const
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
throw
|
|
117
|
+
const uuidSet = this.connections.get(wsSet);
|
|
118
|
+
if (!uuidSet) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (this.registrations.has(uuid)) {
|
|
122
|
+
throw new Error('Already subscribed topic');
|
|
123
|
+
}
|
|
124
|
+
this.registrations.set(uuid, { uuid, target, subscribeBody, webSocketSet: wsSet });
|
|
125
|
+
uuidSet.add(uuid);
|
|
126
|
+
if (!this.targets.get(target)) {
|
|
127
|
+
this.targets.set(target, new Set<string>());
|
|
121
128
|
}
|
|
129
|
+
this.targets.get(target)?.add(uuid);
|
|
122
130
|
}
|
|
123
131
|
|
|
124
132
|
findAllClientsByTarget(target: string): RegisteredClientSet[] {
|
|
133
|
+
const uuids = this.targets.get(target);
|
|
134
|
+
if (!uuids) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
125
137
|
const results: RegisteredClientSet[] = [];
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
if (
|
|
129
|
-
results.push({ ...
|
|
138
|
+
uuids.forEach(uuid => {
|
|
139
|
+
const reg = this.registrations.get(uuid);
|
|
140
|
+
if (reg) {
|
|
141
|
+
results.push({ ...reg });
|
|
130
142
|
}
|
|
131
|
-
}
|
|
143
|
+
});
|
|
132
144
|
return results;
|
|
133
145
|
}
|
|
134
146
|
|
|
135
147
|
findClientsByTarget(target: string): RegisteredClientSet {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
148
|
+
const uuids = this.targets.get(target);
|
|
149
|
+
if (!uuids) {
|
|
150
|
+
return undefined as any;
|
|
151
|
+
}
|
|
152
|
+
for (const uuid of Array.from(uuids)) {
|
|
153
|
+
const reg = this.registrations.get(uuid);
|
|
154
|
+
if (reg) {
|
|
155
|
+
return { ...reg };
|
|
140
156
|
}
|
|
141
157
|
}
|
|
158
|
+
return undefined as any;
|
|
142
159
|
}
|
|
143
160
|
|
|
144
161
|
removeClientByUUID(uuid: string) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
162
|
+
const reg = this.registrations.get(uuid);
|
|
163
|
+
if (!reg) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
this.registrations.delete(uuid);
|
|
167
|
+
this.connections.get(reg.webSocketSet)?.delete(uuid);
|
|
168
|
+
const targetSet = this.targets.get(reg.target);
|
|
169
|
+
if (targetSet) {
|
|
170
|
+
targetSet.delete(uuid);
|
|
171
|
+
if (targetSet.size <= 0) {
|
|
172
|
+
this.targets.delete(reg.target);
|
|
153
173
|
}
|
|
154
174
|
}
|
|
155
|
-
|
|
175
|
+
// 이벤트 구독도 함께 정리
|
|
176
|
+
this.eventSubscriptions.delete(uuid);
|
|
177
|
+
return true;
|
|
156
178
|
}
|
|
157
179
|
|
|
158
180
|
findClientByUUID(uuid: string): RegisteredClientSet | undefined {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (find) {
|
|
162
|
-
return { ...find, webSocketSet: wsSet };
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return undefined;
|
|
181
|
+
const reg = this.registrations.get(uuid);
|
|
182
|
+
return reg ? { ...reg } : undefined;
|
|
166
183
|
}
|
|
167
184
|
|
|
168
185
|
sendDataByUUID({ uuid, body }: { uuid: string; body?: any }) {
|
|
@@ -342,7 +359,7 @@ export class WebSocketManager implements WebSocketEndPoint {
|
|
|
342
359
|
}
|
|
343
360
|
|
|
344
361
|
getOpenClients() {
|
|
345
|
-
return Array.from(this.
|
|
362
|
+
return Array.from(this.connections.keys()).filter(it => it.socket.readyState === WebSocket.OPEN);
|
|
346
363
|
}
|
|
347
364
|
|
|
348
365
|
private isBinaryPayload(value: any) {
|
|
@@ -701,13 +718,24 @@ export class WebSocketManager implements WebSocketEndPoint {
|
|
|
701
718
|
}
|
|
702
719
|
|
|
703
720
|
private removeAllClientsByWebSocketSet(wsSet: WebSocketSet) {
|
|
704
|
-
const
|
|
705
|
-
if (!
|
|
721
|
+
const uuidSet = this.connections.get(wsSet);
|
|
722
|
+
if (!uuidSet) {
|
|
706
723
|
return;
|
|
707
724
|
}
|
|
708
|
-
|
|
709
|
-
this.
|
|
710
|
-
|
|
711
|
-
|
|
725
|
+
uuidSet.forEach(uuid => {
|
|
726
|
+
const reg = this.registrations.get(uuid);
|
|
727
|
+
if (reg) {
|
|
728
|
+
this.registrations.delete(uuid);
|
|
729
|
+
const targetSet = this.targets.get(reg.target);
|
|
730
|
+
if (targetSet) {
|
|
731
|
+
targetSet.delete(uuid);
|
|
732
|
+
if (targetSet.size <= 0) {
|
|
733
|
+
this.targets.delete(reg.target);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
this.eventSubscriptions.delete(uuid);
|
|
738
|
+
});
|
|
739
|
+
uuidSet.clear();
|
|
712
740
|
}
|
|
713
741
|
}
|