@dooboostore/simple-boot-http-server 1.0.26 → 1.0.28
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 +138 -17
- package/dist/cjs/SimpleBootHttpServer.js +33 -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/models/RequestResponse.js +52 -0
- package/dist/cjs/models/RequestResponse.js.map +3 -3
- package/dist/cjs/proxy/RequestResponseInjectProxy.js +35 -0
- package/dist/cjs/proxy/RequestResponseInjectProxy.js.map +7 -0
- package/dist/cjs/proxy/SymbolIntentApiServiceProxy.js +1 -1
- package/dist/cjs/proxy/SymbolIntentApiServiceProxy.js.map +2 -2
- package/dist/cjs/proxy/index.js +3 -1
- package/dist/cjs/proxy/index.js.map +2 -2
- package/dist/cjs/resolvers/Resolver.js.map +1 -1
- package/dist/cjs/websocket/WebSocketManager.js +68 -40
- package/dist/cjs/websocket/WebSocketManager.js.map +2 -2
- package/dist/esm/SimpleBootHttpServer.js +34 -17
- 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/models/RequestResponse.js +52 -0
- package/dist/esm/models/RequestResponse.js.map +2 -2
- package/dist/esm/proxy/RequestResponseInjectProxy.js +12 -0
- package/dist/esm/proxy/RequestResponseInjectProxy.js.map +7 -0
- package/dist/esm/proxy/SymbolIntentApiServiceProxy.js +1 -1
- package/dist/esm/proxy/SymbolIntentApiServiceProxy.js.map +2 -2
- package/dist/esm/proxy/index.js +1 -0
- package/dist/esm/proxy/index.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 +572 -387
- 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 +13 -8
- package/dist/types/decorators/MethodMapping.d.ts.map +1 -1
- package/dist/types/filters/ResourceFilter.d.ts.map +1 -1
- package/dist/types/models/RequestResponse.d.ts +9 -0
- package/dist/types/models/RequestResponse.d.ts.map +1 -1
- package/dist/types/proxy/RequestResponseInjectProxy.d.ts +22 -0
- package/dist/types/proxy/RequestResponseInjectProxy.d.ts.map +1 -0
- package/dist/types/proxy/index.d.ts +1 -0
- package/dist/types/proxy/index.d.ts.map +1 -1
- package/dist/types/resolvers/Resolver.d.ts +2 -1
- package/dist/types/resolvers/Resolver.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 +572 -387
- package/dist/umd-bundle/dooboostore-simple-boot-http-server.umd.js.map +4 -4
- package/dist/umd-bundle/websocket-client.umd.js +315 -149
- package/dist/umd-bundle/websocket-client.umd.js.map +4 -4
- package/package.json +9 -9
- package/src/SimpleBootHttpServer.ts +38 -17
- package/src/codes/Mimes.ts +2 -0
- package/src/decorators/MethodMapping.ts +151 -152
- package/src/filters/ResourceFilter.ts +0 -1
- package/src/models/RequestResponse.ts +50 -0
- package/src/proxy/RequestResponseInjectProxy.ts +30 -0
- package/src/proxy/SymbolIntentApiServiceProxy.ts +1 -1
- package/src/proxy/index.ts +1 -0
- package/src/resolvers/Resolver.ts +2 -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.28",
|
|
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.33",
|
|
102
|
+
"@dooboostore/core-node": "1.0.14",
|
|
103
|
+
"@dooboostore/dom-render": "1.0.115",
|
|
104
|
+
"@dooboostore/core-web": "1.0.14",
|
|
105
|
+
"@dooboostore/simple-boot": "1.0.58"
|
|
106
106
|
},
|
|
107
107
|
"scripts": {
|
|
108
108
|
"clean": "rm -rf dist",
|
|
@@ -121,6 +121,6 @@
|
|
|
121
121
|
"publish:npm": "pnpm run build && cp package.json package.json.back && pnpm pack && TARBALL_NAME=$(ls dooboostore-simple-boot-http-server-*.tgz | head -n 1) && tar -xzf \"$TARBALL_NAME\" -C . --strip-components=1 package/package.json && rm \"$TARBALL_NAME\" && (pnpm publish --no-git-checks || true) && cp package.json.back package.json && rm -rf package.json.back ",
|
|
122
122
|
"ts:clean": "find . -name '*.js' -o -name '*.map' -o -name '*.d.ts' | xargs rm -f",
|
|
123
123
|
"typecheck": "tsc --noEmit",
|
|
124
|
-
"test": "
|
|
124
|
+
"test": "node --import ./test/register-loader.mjs --test --test-force-exit test/index.test.ts"
|
|
125
125
|
}
|
|
126
126
|
}
|
|
@@ -13,7 +13,7 @@ import {Filter} from './filters/Filter';
|
|
|
13
13
|
import {ExceptionHandlerSituationType, targetExceptionHandler} from '@dooboostore/simple-boot';
|
|
14
14
|
import {getInject, SituationTypeContainer, SituationTypeContainers} from '@dooboostore/simple-boot';
|
|
15
15
|
import {EndPoint} from './endpoints/EndPoint';
|
|
16
|
-
import {ReflectUtils} from '@dooboostore/core';
|
|
16
|
+
import {ReflectUtils, ValidUtils} from '@dooboostore/core';
|
|
17
17
|
import {ReqFormUrlBody} from './models/datas/body/ReqFormUrlBody';
|
|
18
18
|
import {ReqJsonBody} from './models/datas/body/ReqJsonBody';
|
|
19
19
|
import {ReqHeader} from './models/datas/ReqHeader';
|
|
@@ -140,6 +140,13 @@ export class SimpleBootHttpServer extends SimpleApplication {
|
|
|
140
140
|
otherStorage.set(RequestResponse, rr);
|
|
141
141
|
otherStorage.set(IncomingMessage, req);
|
|
142
142
|
otherStorage.set(ServerResponse, res);
|
|
143
|
+
// 클라이언트가 응답 완료 전에 끊으면 abort. 핸들러는 파라미터 타입 AbortSignal 로 주입받아
|
|
144
|
+
// fetch/timers/events.once 등에 넘기면 대기 중이어도 바로 풀린다 (제너레이터 스트림의 이벤트 대기 누수 방지)
|
|
145
|
+
const abortController = new AbortController();
|
|
146
|
+
res.once('close', () => {
|
|
147
|
+
if (!res.writableFinished) abortController.abort();
|
|
148
|
+
});
|
|
149
|
+
otherStorage.set(AbortSignal, abortController.signal);
|
|
143
150
|
try {
|
|
144
151
|
transactionManager?.try();
|
|
145
152
|
if (this.option.requestEndPoints) {
|
|
@@ -326,23 +333,37 @@ export class SimpleBootHttpServer extends SimpleApplication {
|
|
|
326
333
|
여기에서 정해진 타입 리턴에 따른 조치
|
|
327
334
|
...
|
|
328
335
|
*/
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
336
|
+
const resConfig = it.config?.res;
|
|
337
|
+
if (resConfig === 'manual') {
|
|
338
|
+
// 핸들러가 ServerResponse를 직접 주입받아 스스로 write/end를 제어한다 (예: SSE 스트리밍)
|
|
339
|
+
} else if (ValidUtils.isGenerator(data) || ValidUtils.isAsyncGenerator(data)) {
|
|
340
|
+
// 제너레이터 리턴: 헤더 먼저 보내고, yield 된 값을 하나씩 바로 body 로 흘려보낸다 (예: SSE)
|
|
341
|
+
const headers = resConfig?.header ?? {};
|
|
342
|
+
if (resConfig?.contentType) {
|
|
343
|
+
headers[HttpHeaders.ContentType] = resConfig.contentType;
|
|
344
|
+
}
|
|
345
|
+
rr.resSetHeaders(headers);
|
|
346
|
+
rr.resSetStatusCode(resConfig?.status ?? HttpStatus.Ok);
|
|
347
|
+
await rr.resStream(data);
|
|
348
|
+
} else {
|
|
349
|
+
if (it.config?.resolver) {
|
|
350
|
+
const execute = typeof it.config.resolver === 'function' ? this.simstanceManager.getOrNewSim({target: it.config.resolver}) : it.config.resolver;
|
|
351
|
+
data = await execute?.resolve?.(data, rr, resConfig);
|
|
352
|
+
}
|
|
353
|
+
const status = resConfig?.status ?? HttpStatus.Ok;
|
|
354
|
+
const headers = resConfig?.header ?? {};
|
|
355
|
+
if (resConfig?.contentType) {
|
|
356
|
+
headers[HttpHeaders.ContentType] = resConfig.contentType;
|
|
357
|
+
}
|
|
358
|
+
if ((resConfig?.contentType?.toLowerCase().indexOf(Mimes.ApplicationJson.toLowerCase()) ?? -1) > -1) {
|
|
359
|
+
data = JSON.stringify(data);
|
|
360
|
+
} else if (data && typeof data === 'object') {
|
|
361
|
+
data = JSON.stringify(data);
|
|
362
|
+
}
|
|
363
|
+
rr.resSetHeaders(headers)
|
|
364
|
+
rr.resSetStatusCode(status);
|
|
365
|
+
rr.resWrite(data);
|
|
342
366
|
}
|
|
343
|
-
rr.resSetHeaders(headers)
|
|
344
|
-
rr.resSetStatusCode(status);
|
|
345
|
-
rr.resWrite(data);
|
|
346
367
|
}
|
|
347
368
|
}
|
|
348
369
|
|
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
|
}
|
|
@@ -6,234 +6,233 @@ import { Mimes } from '../codes/Mimes';
|
|
|
6
6
|
import { HttpMethod } from '../codes/HttpMethod';
|
|
7
7
|
|
|
8
8
|
export enum UrlMappingSituationType {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
export type
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
REQ_JSON_BODY = 'SIMPLE_BOOT_HTTP_SERVER://URLMAPPING/REQ_JSON_BODY',
|
|
10
|
+
REQ_PATH_DATA = 'SIMPLE_BOOT_HTTP_SERVER://URLMAPPING/REQ_PATH_DATA',
|
|
11
|
+
// REQ_URL_SEARCH_PARAMS = 'SIMPLE_BOOT_HTTP_SERVER://URLMAPPING/REQ_URL_SEARCH_PARAMS',
|
|
12
|
+
REQ_URL_SEARCH_PARAMS_OBJ = 'SIMPLE_BOOT_HTTP_SERVER://URLMAPPING/REQ_URL_SEARCH_PARAMS_OBJ',
|
|
13
|
+
REQ_FORM_URL_BODY = 'SIMPLE_BOOT_HTTP_SERVER://URLMAPPING/REQ_FORM_URL_BODY'
|
|
14
|
+
}
|
|
15
|
+
export type RequestMappingConfig = {
|
|
16
|
+
method: HttpMethod | string;
|
|
17
|
+
description?: {
|
|
18
|
+
name?: string;
|
|
19
|
+
detail?: string;
|
|
20
|
+
};
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
export type SaveMappingConfig = {propertyKey: string | symbol; config: MappingConfig}
|
|
22
|
+
req?: {
|
|
23
|
+
contentType?: (Mimes | string)[];
|
|
24
|
+
accept?: (Mimes | string)[];
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
export type ResponseMappingConfig = {
|
|
28
|
+
status?: number;
|
|
29
|
+
header?: { [key: string]: string };
|
|
30
|
+
contentType?: Mimes | string;
|
|
31
|
+
};
|
|
32
|
+
export type MappingConfig = (RequestMappingConfig & { res?: ResponseMappingConfig; resolver?: Resolver | ConstructorType<Resolver> }) | (RequestMappingConfig & { res: 'manual' });
|
|
33
|
+
export type SaveMappingConfig = { propertyKey: string | symbol; config: MappingConfig };
|
|
35
34
|
const MappingMetadataKey = Symbol.for('simple-boot-http-server:mapping-metadata');
|
|
36
35
|
// const GETSMappingMetadataKey = Symbol('GET_METHODS');
|
|
37
36
|
// const GETMappingMetadataKey = Symbol('GET_METHOD');
|
|
38
37
|
|
|
39
38
|
const process = (config: MappingConfig, target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
39
|
+
const saveMappingConfigs = (ReflectUtils.getMetadata(MappingMetadataKey, target.constructor) ?? []) as SaveMappingConfig[];
|
|
40
|
+
saveMappingConfigs.push({ propertyKey, config });
|
|
41
|
+
ReflectUtils.defineMetadata(MappingMetadataKey, saveMappingConfigs, target.constructor);
|
|
42
|
+
ReflectUtils.defineMetadata(MappingMetadataKey, config, target, propertyKey);
|
|
43
|
+
};
|
|
45
44
|
export function UrlMapping(config: MappingConfig): ReflectMethod;
|
|
46
45
|
export function UrlMapping(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
|
|
47
46
|
export function UrlMapping(configOrtarget?: MappingConfig | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
if (propertyKey && descriptor) {
|
|
48
|
+
process({ method: HttpMethod.GET }, configOrtarget, propertyKey, descriptor);
|
|
49
|
+
} else {
|
|
50
|
+
return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
|
|
51
|
+
process(configOrtarget, target, propertyKey, descriptor);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
55
54
|
}
|
|
56
55
|
export const getUrlMapping = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
|
|
57
|
-
|
|
58
|
-
}
|
|
56
|
+
return ReflectUtils.getMetadata(MappingMetadataKey, target, propertyKey);
|
|
57
|
+
};
|
|
59
58
|
|
|
60
59
|
export const getUrlMappings = (target: any): SaveMappingConfig[] => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
60
|
+
if (target !== null && undefined !== target && typeof target === 'object') {
|
|
61
|
+
target = target.constructor;
|
|
62
|
+
}
|
|
63
|
+
return ReflectUtils.getMetadata(MappingMetadataKey, target);
|
|
64
|
+
};
|
|
66
65
|
export function GET(config: Omit<MappingConfig, 'method'>): ReflectMethod;
|
|
67
66
|
export function GET(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
|
|
68
67
|
export function GET(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
68
|
+
if (propertyKey && descriptor) {
|
|
69
|
+
process({ method: HttpMethod.GET }, configOrTarget, propertyKey, descriptor);
|
|
70
|
+
} else {
|
|
71
|
+
return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
|
|
72
|
+
configOrTarget.method = HttpMethod.GET;
|
|
73
|
+
process(configOrTarget, target, propertyKey, descriptor);
|
|
74
|
+
};
|
|
75
|
+
}
|
|
77
76
|
}
|
|
78
77
|
|
|
79
78
|
export const getGET = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
|
|
80
|
-
|
|
81
|
-
}
|
|
79
|
+
return getUrlMapping(target, propertyKey);
|
|
80
|
+
};
|
|
82
81
|
export const getGETS = (target: any): SaveMappingConfig[] => {
|
|
83
|
-
|
|
84
|
-
}
|
|
82
|
+
return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.GET);
|
|
83
|
+
};
|
|
85
84
|
export function POST(config: Omit<MappingConfig, 'method'>): ReflectMethod;
|
|
86
85
|
export function POST(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
|
|
87
86
|
export function POST(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
87
|
+
if (propertyKey && descriptor) {
|
|
88
|
+
process({ method: HttpMethod.POST }, configOrTarget, propertyKey, descriptor);
|
|
89
|
+
} else {
|
|
90
|
+
return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
|
|
91
|
+
configOrTarget.method = HttpMethod.POST;
|
|
92
|
+
process(configOrTarget, target, propertyKey, descriptor);
|
|
93
|
+
};
|
|
94
|
+
}
|
|
96
95
|
}
|
|
97
96
|
|
|
98
97
|
export const getPOST = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
|
|
99
|
-
|
|
100
|
-
}
|
|
98
|
+
return getUrlMapping(target, propertyKey);
|
|
99
|
+
};
|
|
101
100
|
export const getPOSTS = (target: any): SaveMappingConfig[] => {
|
|
102
|
-
|
|
103
|
-
}
|
|
101
|
+
return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.POST);
|
|
102
|
+
};
|
|
104
103
|
export function DELETE(config: Omit<MappingConfig, 'method'>): ReflectMethod;
|
|
105
104
|
export function DELETE(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
|
|
106
105
|
export function DELETE(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
106
|
+
if (propertyKey && descriptor) {
|
|
107
|
+
process({ method: HttpMethod.DELETE }, configOrTarget, propertyKey, descriptor);
|
|
108
|
+
} else {
|
|
109
|
+
return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
|
|
110
|
+
configOrTarget.method = HttpMethod.DELETE;
|
|
111
|
+
process(configOrTarget, target, propertyKey, descriptor);
|
|
112
|
+
};
|
|
113
|
+
}
|
|
115
114
|
}
|
|
116
115
|
|
|
117
116
|
export const getDELETE = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
|
|
118
|
-
|
|
119
|
-
}
|
|
117
|
+
return getUrlMapping(target, propertyKey);
|
|
118
|
+
};
|
|
120
119
|
export const getDELETES = (target: any): SaveMappingConfig[] => {
|
|
121
|
-
|
|
122
|
-
}
|
|
120
|
+
return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.DELETE);
|
|
121
|
+
};
|
|
123
122
|
export function PUT(config: Omit<MappingConfig, 'method'>): ReflectMethod;
|
|
124
123
|
export function PUT(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
|
|
125
124
|
export function PUT(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
125
|
+
if (propertyKey && descriptor) {
|
|
126
|
+
process({ method: HttpMethod.PUT }, configOrTarget, propertyKey, descriptor);
|
|
127
|
+
} else {
|
|
128
|
+
return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
|
|
129
|
+
configOrTarget.method = HttpMethod.PUT;
|
|
130
|
+
process(configOrTarget, target, propertyKey, descriptor);
|
|
131
|
+
};
|
|
132
|
+
}
|
|
134
133
|
}
|
|
135
134
|
|
|
136
135
|
export const getPUT = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
|
|
137
|
-
|
|
138
|
-
}
|
|
136
|
+
return getUrlMapping(target, propertyKey);
|
|
137
|
+
};
|
|
139
138
|
export const getPUTS = (target: any): SaveMappingConfig[] => {
|
|
140
|
-
|
|
141
|
-
}
|
|
139
|
+
return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.PUT);
|
|
140
|
+
};
|
|
142
141
|
export function PATCH(config: Omit<MappingConfig, 'method'>): ReflectMethod;
|
|
143
142
|
export function PATCH(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
|
|
144
143
|
export function PATCH(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
144
|
+
if (propertyKey && descriptor) {
|
|
145
|
+
process({ method: HttpMethod.PATCH }, configOrTarget, propertyKey, descriptor);
|
|
146
|
+
} else {
|
|
147
|
+
return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
|
|
148
|
+
configOrTarget.method = HttpMethod.PATCH;
|
|
149
|
+
process(configOrTarget, target, propertyKey, descriptor);
|
|
150
|
+
};
|
|
151
|
+
}
|
|
153
152
|
}
|
|
154
153
|
|
|
155
154
|
export const getPATCH = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
|
|
156
|
-
|
|
157
|
-
}
|
|
155
|
+
return getUrlMapping(target, propertyKey);
|
|
156
|
+
};
|
|
158
157
|
export const getPATCHS = (target: any): SaveMappingConfig[] => {
|
|
159
|
-
|
|
160
|
-
}
|
|
158
|
+
return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.PATCH);
|
|
159
|
+
};
|
|
161
160
|
export function OPTIONS(config: Omit<MappingConfig, 'method'>): ReflectMethod;
|
|
162
161
|
export function OPTIONS(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
|
|
163
162
|
export function OPTIONS(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
163
|
+
if (propertyKey && descriptor) {
|
|
164
|
+
process({ method: HttpMethod.OPTIONS }, configOrTarget, propertyKey, descriptor);
|
|
165
|
+
} else {
|
|
166
|
+
return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
|
|
167
|
+
configOrTarget.method = HttpMethod.OPTIONS;
|
|
168
|
+
process(configOrTarget, target, propertyKey, descriptor);
|
|
169
|
+
};
|
|
170
|
+
}
|
|
172
171
|
}
|
|
173
172
|
|
|
174
173
|
export const getOPTIONS = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
|
|
175
|
-
|
|
176
|
-
}
|
|
174
|
+
return getUrlMapping(target, propertyKey);
|
|
175
|
+
};
|
|
177
176
|
export const getOPTIONSS = (target: any): SaveMappingConfig[] => {
|
|
178
|
-
|
|
179
|
-
}
|
|
177
|
+
return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.OPTIONS);
|
|
178
|
+
};
|
|
180
179
|
export function HEAD(config: Omit<MappingConfig, 'method'>): ReflectMethod;
|
|
181
180
|
export function HEAD(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
|
|
182
181
|
export function HEAD(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
182
|
+
if (propertyKey && descriptor) {
|
|
183
|
+
process({ method: HttpMethod.HEAD }, configOrTarget, propertyKey, descriptor);
|
|
184
|
+
} else {
|
|
185
|
+
return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
|
|
186
|
+
configOrTarget.method = HttpMethod.HEAD;
|
|
187
|
+
process(configOrTarget, target, propertyKey, descriptor);
|
|
188
|
+
};
|
|
189
|
+
}
|
|
191
190
|
}
|
|
192
191
|
|
|
193
192
|
export const getHEAD = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
|
|
194
|
-
|
|
195
|
-
}
|
|
193
|
+
return getUrlMapping(target, propertyKey);
|
|
194
|
+
};
|
|
196
195
|
export const getHEADS = (target: any): SaveMappingConfig[] => {
|
|
197
|
-
|
|
198
|
-
}
|
|
196
|
+
return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.HEAD);
|
|
197
|
+
};
|
|
199
198
|
export function TRACE(config: Omit<MappingConfig, 'method'>): ReflectMethod;
|
|
200
199
|
export function TRACE(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
|
|
201
200
|
export function TRACE(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
201
|
+
if (propertyKey && descriptor) {
|
|
202
|
+
process({ method: HttpMethod.TRACE }, configOrTarget, propertyKey, descriptor);
|
|
203
|
+
} else {
|
|
204
|
+
return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
|
|
205
|
+
configOrTarget.method = HttpMethod.TRACE;
|
|
206
|
+
process(configOrTarget, target, propertyKey, descriptor);
|
|
207
|
+
};
|
|
208
|
+
}
|
|
210
209
|
}
|
|
211
210
|
|
|
212
211
|
export const getTRACE = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
|
|
213
|
-
|
|
214
|
-
}
|
|
212
|
+
return getUrlMapping(target, propertyKey);
|
|
213
|
+
};
|
|
215
214
|
export const getTRACES = (target: any): SaveMappingConfig[] => {
|
|
216
|
-
|
|
217
|
-
}
|
|
215
|
+
return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.TRACE);
|
|
216
|
+
};
|
|
218
217
|
export function CONNECT(config: Omit<MappingConfig, 'method'>): ReflectMethod;
|
|
219
218
|
export function CONNECT(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
|
|
220
219
|
export function CONNECT(configOrTarget?: Omit<MappingConfig, 'method'> | any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): ReflectMethod | void {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
220
|
+
if (propertyKey && descriptor) {
|
|
221
|
+
process({ method: HttpMethod.CONNECT }, configOrTarget, propertyKey, descriptor);
|
|
222
|
+
} else {
|
|
223
|
+
return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
|
|
224
|
+
configOrTarget.method = HttpMethod.CONNECT;
|
|
225
|
+
process(configOrTarget, target, propertyKey, descriptor);
|
|
226
|
+
};
|
|
227
|
+
}
|
|
229
228
|
}
|
|
230
229
|
|
|
231
230
|
export const getCONNECT = (target: any, propertyKey: string | symbol): MappingConfig | undefined => {
|
|
232
|
-
|
|
233
|
-
}
|
|
231
|
+
return getUrlMapping(target, propertyKey);
|
|
232
|
+
};
|
|
234
233
|
export const getCONNECTS = (target: any): SaveMappingConfig[] => {
|
|
235
|
-
|
|
236
|
-
}
|
|
234
|
+
return getUrlMappings(target)?.filter(it => it.config?.method.toUpperCase() === HttpMethod.CONNECT);
|
|
235
|
+
};
|
|
237
236
|
|
|
238
237
|
// Aliases for lowercase
|
|
239
238
|
export const urlMapping = UrlMapping;
|
|
@@ -11,6 +11,7 @@ import { ReqHeader } from './datas/ReqHeader';
|
|
|
11
11
|
import { ReqMultipartFormBody } from './datas/body/ReqMultipartFormBody';
|
|
12
12
|
import { HttpStatus } from '../codes/HttpStatus';
|
|
13
13
|
import { gzip } from 'node-gzip';
|
|
14
|
+
import { createGzip, constants as zlibConstants } from 'zlib';
|
|
14
15
|
import { SessionManager } from '../session/SessionManager';
|
|
15
16
|
import { HttpServerOption } from '../option/HttpServerOption';
|
|
16
17
|
import { Blob } from 'node:buffer';
|
|
@@ -557,6 +558,55 @@ export class RequestResponse {
|
|
|
557
558
|
this.res.end();
|
|
558
559
|
}
|
|
559
560
|
|
|
561
|
+
/**
|
|
562
|
+
* 헤더를 즉시 보내고, source 가 내놓는 값을 하나씩 바로 body 에 쓴 뒤 end.
|
|
563
|
+
* (SSE / 스트리밍 응답용. resWrite 는 버퍼링 후 resEnd 에서 한 번에 보내므로 스트리밍 불가)
|
|
564
|
+
* - 문자열/Buffer 는 그대로, 그 외는 JSON.stringify
|
|
565
|
+
* - Accept-Encoding 에 gzip 이 있으면 gzip 스트림으로 압축하고, 청크마다 Z_SYNC_FLUSH 로 바로 내보낸다
|
|
566
|
+
* (flush 없이 쓰면 zlib 내부 버퍼에 모였다가 늦게 나가서 SSE 실시간성이 깨짐)
|
|
567
|
+
* - 클라이언트가 끊으면 source.return() 으로 제너레이터를 종료시켜 finally 가 돌게 한다
|
|
568
|
+
*/
|
|
569
|
+
async resStream(source: Iterable<any> | AsyncIterable<any>) {
|
|
570
|
+
const res = this.res;
|
|
571
|
+
const gz = this.reqHeaderFirst(HttpHeaders.AcceptEncoding)?.includes('gzip') ? createGzip() : undefined;
|
|
572
|
+
if (gz) {
|
|
573
|
+
res.setHeader(HttpHeaders.ContentEncoding, 'gzip');
|
|
574
|
+
res.removeHeader(HttpHeaders.ContentLength);
|
|
575
|
+
gz.pipe(res);
|
|
576
|
+
}
|
|
577
|
+
const out = gz ?? res;
|
|
578
|
+
res.flushHeaders();
|
|
579
|
+
let closed = false;
|
|
580
|
+
const onClose = () => {
|
|
581
|
+
closed = true;
|
|
582
|
+
(source as any).return?.();
|
|
583
|
+
};
|
|
584
|
+
res.once('close', onClose);
|
|
585
|
+
try {
|
|
586
|
+
for await (const chunk of source) {
|
|
587
|
+
if (closed) break;
|
|
588
|
+
const data = typeof chunk === 'string' || Buffer.isBuffer(chunk) ? chunk : JSON.stringify(chunk);
|
|
589
|
+
const ok = out.write(data);
|
|
590
|
+
gz?.flush(zlibConstants.Z_SYNC_FLUSH);
|
|
591
|
+
// backpressure: 버퍼가 차면 drain(또는 연결 종료)까지 대기
|
|
592
|
+
if (!ok) await new Promise(resolve => { out.once('drain', resolve); res.once('close', resolve); });
|
|
593
|
+
}
|
|
594
|
+
} catch (e) {
|
|
595
|
+
// 클라이언트가 끊긴 뒤의 에러(AbortSignal 로 대기를 푼 AbortError 등)는 받을 상대가 없으니 삼킨다
|
|
596
|
+
if (!closed) throw e;
|
|
597
|
+
} finally {
|
|
598
|
+
res.off('close', onClose);
|
|
599
|
+
if (closed) {
|
|
600
|
+
gz?.destroy();
|
|
601
|
+
} else if (!res.writableEnded) {
|
|
602
|
+
// gzip 이면 gz.end() → pipe 가 res.end() 까지 해준다. 끝까지 흘려보낸 뒤 리턴
|
|
603
|
+
const finished = new Promise(resolve => { res.once('finish', resolve); res.once('close', resolve); });
|
|
604
|
+
out.end();
|
|
605
|
+
await finished;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
560
610
|
resIsDone() {
|
|
561
611
|
return this.res.finished || this.res.writableEnded || this.res.headersSent;
|
|
562
612
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { RequestResponse } from '../models/RequestResponse';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SSR 인프로세스 렌더용 sim 래퍼.
|
|
5
|
+
*
|
|
6
|
+
* 클라이언트는 SymbolIntentApiServiceProxy 가 HTTP 로 호출하며 IntentSchemeFilter 가
|
|
7
|
+
* intent.data 마지막에 rr(RequestResponse) 을 붙여 백서비스의 data 인자로 전달한다.
|
|
8
|
+
* 반면 SSR 은 페이지가 sim 을 인프로세스로 직접 호출(`svc.method(request)`)하므로 rr 이 없다.
|
|
9
|
+
*
|
|
10
|
+
* 이 프록시는 그 공백을 메운다 — 메서드 호출 시 넘어온 인자 **뒤에 rr 을 붙여준다**.
|
|
11
|
+
* IntentSchemeFilter 규약과 동일하게:
|
|
12
|
+
* - 인자 0개 → (rr) // 본문·쿼리 없는 요청과 동일
|
|
13
|
+
* - 인자 1개 → (request, rr) // 일반 서비스 호출
|
|
14
|
+
* - 인자 N개 → (...args, rr) // 이미 data 를 넘겼으면 그건 그대로 쓰이고 rr 은 뒤에 무시됨
|
|
15
|
+
*
|
|
16
|
+
* 백서비스 메서드가 `data?: RequestResponse` 옵셔널이라 fn.length 로 arity 를 못 재므로
|
|
17
|
+
* (옵셔널은 length 에서 제외) "메서드 파라미터 수"가 아니라 "호출 인자 뒤 append" 방식을 쓴다.
|
|
18
|
+
*
|
|
19
|
+
* constructor / 비함수 프로퍼티는 감싸지 않는다 — Sim 이 it.constructor 로 타입·메타데이터를 찾기 때문.
|
|
20
|
+
* rr 이 없으면(비SSR) 인자를 그대로 흘려보낸다.
|
|
21
|
+
*/
|
|
22
|
+
export const injectRequestResponse = <T extends object>(sim: T, rr?: RequestResponse): T =>
|
|
23
|
+
new Proxy(sim, {
|
|
24
|
+
get(target: any, prop: string | symbol) {
|
|
25
|
+
const value = target[prop];
|
|
26
|
+
if (prop === 'constructor' || typeof value !== 'function') return value;
|
|
27
|
+
if (!rr) return value.bind(target);
|
|
28
|
+
return (...args: any[]) => value.apply(target, [...args, rr]);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
@@ -40,7 +40,7 @@ export const createHandler = <T extends object>(apiService: ApiService): Proxy
|
|
|
40
40
|
fetch: {
|
|
41
41
|
credentials: 'include',
|
|
42
42
|
headers: headers,
|
|
43
|
-
body: ConvertUtils.toFormData(userConfig
|
|
43
|
+
body: userConfig?.body ? ConvertUtils.toFormData(userConfig.body) : undefined
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
});
|
package/src/proxy/index.ts
CHANGED