@webda/core 4.0.0-beta.6 → 4.0.0-beta.7
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 +6 -5
- package/lib/application/application.d.ts.map +1 -1
- package/lib/application/application.js +6 -5
- package/lib/application/application.js.map +1 -1
- package/lib/application/iconfiguration.d.ts +31 -0
- package/lib/application/iconfiguration.d.ts.map +1 -1
- package/lib/application/iconfiguration.js.map +1 -1
- package/lib/application/unpackedapplication.d.ts.map +1 -1
- package/lib/application/unpackedapplication.js +2 -1
- package/lib/application/unpackedapplication.js.map +1 -1
- package/lib/bin/cli-phase.d.ts +18 -2
- package/lib/bin/cli-phase.d.ts.map +1 -1
- package/lib/bin/cli-phase.js +25 -0
- package/lib/bin/cli-phase.js.map +1 -1
- package/lib/bin/cli.d.ts +8 -0
- package/lib/bin/cli.d.ts.map +1 -1
- package/lib/bin/cli.js +94 -35
- package/lib/bin/cli.js.map +1 -1
- package/lib/configurations/configuration.service.d.ts +7 -0
- package/lib/configurations/configuration.service.d.ts.map +1 -1
- package/lib/configurations/configuration.service.js +18 -5
- package/lib/configurations/configuration.service.js.map +1 -1
- package/lib/configurations/kubernetesconfiguration.service.d.ts.map +1 -1
- package/lib/configurations/kubernetesconfiguration.service.js +1 -4
- package/lib/configurations/kubernetesconfiguration.service.js.map +1 -1
- package/lib/contexts/httpcontext.d.ts +15 -1
- package/lib/contexts/httpcontext.d.ts.map +1 -1
- package/lib/contexts/httpcontext.js +61 -19
- package/lib/contexts/httpcontext.js.map +1 -1
- package/lib/contexts/operationcontext.d.ts +6 -0
- package/lib/contexts/operationcontext.d.ts.map +1 -1
- package/lib/contexts/operationcontext.js +16 -2
- package/lib/contexts/operationcontext.js.map +1 -1
- package/lib/contexts/restcontext.d.ts +110 -0
- package/lib/contexts/restcontext.d.ts.map +1 -0
- package/lib/contexts/restcontext.js +247 -0
- package/lib/contexts/restcontext.js.map +1 -0
- package/lib/contexts/streamingcontext.d.ts +71 -0
- package/lib/contexts/streamingcontext.d.ts.map +1 -0
- package/lib/contexts/streamingcontext.js +93 -0
- package/lib/contexts/streamingcontext.js.map +1 -0
- package/lib/contexts/webcontext.d.ts +20 -0
- package/lib/contexts/webcontext.d.ts.map +1 -1
- package/lib/contexts/webcontext.js +43 -10
- package/lib/contexts/webcontext.js.map +1 -1
- package/lib/contexts/websocketcontext.d.ts +39 -0
- package/lib/contexts/websocketcontext.d.ts.map +1 -0
- package/lib/contexts/websocketcontext.js +67 -0
- package/lib/contexts/websocketcontext.js.map +1 -0
- package/lib/core/asyncqueue.d.ts +50 -0
- package/lib/core/asyncqueue.d.ts.map +1 -0
- package/lib/core/asyncqueue.js +96 -0
- package/lib/core/asyncqueue.js.map +1 -0
- package/lib/core/core.d.ts +18 -0
- package/lib/core/core.d.ts.map +1 -1
- package/lib/core/core.js +72 -7
- package/lib/core/core.js.map +1 -1
- package/lib/core/icore.d.ts +14 -1
- package/lib/core/icore.d.ts.map +1 -1
- package/lib/core/icore.js.map +1 -1
- package/lib/core/instancestorage.d.ts +1 -0
- package/lib/core/instancestorage.d.ts.map +1 -1
- package/lib/core/instancestorage.js +1 -0
- package/lib/core/instancestorage.js.map +1 -1
- package/lib/core/operations.d.ts +49 -6
- package/lib/core/operations.d.ts.map +1 -1
- package/lib/core/operations.js +187 -24
- package/lib/core/operations.js.map +1 -1
- package/lib/deployers/git.d.ts +4 -2
- package/lib/deployers/git.d.ts.map +1 -1
- package/lib/deployers/git.js +44 -5
- package/lib/deployers/git.js.map +1 -1
- package/lib/errors/errors.d.ts +15 -0
- package/lib/errors/errors.d.ts.map +1 -1
- package/lib/errors/errors.js +20 -0
- package/lib/errors/errors.js.map +1 -1
- package/lib/index.d.ts +5 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +5 -0
- package/lib/index.js.map +1 -1
- package/lib/rest/restoperationstransport.service.d.ts +108 -0
- package/lib/rest/restoperationstransport.service.d.ts.map +1 -1
- package/lib/rest/restoperationstransport.service.js +348 -9
- package/lib/rest/restoperationstransport.service.js.map +1 -1
- package/lib/rest/router.service.d.ts +1 -1
- package/lib/rest/router.service.d.ts.map +1 -1
- package/lib/rest/router.service.js.map +1 -1
- package/lib/services/authenticatedencryption.d.ts +25 -0
- package/lib/services/authenticatedencryption.d.ts.map +1 -0
- package/lib/services/authenticatedencryption.js +56 -0
- package/lib/services/authenticatedencryption.js.map +1 -0
- package/lib/services/cloudbinary.d.ts.map +1 -1
- package/lib/services/cloudbinary.js +3 -2
- package/lib/services/cloudbinary.js.map +1 -1
- package/lib/services/command.d.ts +7 -1
- package/lib/services/command.d.ts.map +1 -1
- package/lib/services/command.js +4 -0
- package/lib/services/command.js.map +1 -1
- package/lib/services/cryptoservice.service.d.ts +405 -25
- package/lib/services/cryptoservice.service.d.ts.map +1 -1
- package/lib/services/cryptoservice.service.js +779 -72
- package/lib/services/cryptoservice.service.js.map +1 -1
- package/lib/services/httpserver.service.d.ts.map +1 -1
- package/lib/services/httpserver.service.js +15 -9
- package/lib/services/httpserver.service.js.map +1 -1
- package/lib/services/icryptoservice.d.ts +11 -0
- package/lib/services/icryptoservice.d.ts.map +1 -1
- package/lib/services/icryptoservice.js +2 -0
- package/lib/services/icryptoservice.js.map +1 -1
- package/lib/services/mailer.service.d.ts +4 -0
- package/lib/services/mailer.service.d.ts.map +1 -1
- package/lib/services/mailer.service.js +7 -1
- package/lib/services/mailer.service.js.map +1 -1
- package/lib/services/operationstransport.d.ts +13 -0
- package/lib/services/operationstransport.d.ts.map +1 -1
- package/lib/services/operationstransport.js +28 -6
- package/lib/services/operationstransport.js.map +1 -1
- package/lib/services/pki.encrypter.d.ts +214 -0
- package/lib/services/pki.encrypter.d.ts.map +1 -0
- package/lib/services/pki.encrypter.js +350 -0
- package/lib/services/pki.encrypter.js.map +1 -0
- package/lib/services/secretscheck.d.ts +120 -0
- package/lib/services/secretscheck.d.ts.map +1 -0
- package/lib/services/secretscheck.js +322 -0
- package/lib/services/secretscheck.js.map +1 -0
- package/lib/services/servicecommands.d.ts +8 -4
- package/lib/services/servicecommands.d.ts.map +1 -1
- package/lib/services/servicecommands.js +14 -5
- package/lib/services/servicecommands.js.map +1 -1
- package/lib/session/manager.model.d.ts +3 -1
- package/lib/session/manager.model.d.ts.map +1 -1
- package/lib/session/manager.model.js +6 -3
- package/lib/session/manager.model.js.map +1 -1
- package/lib/test/objects.d.ts.map +1 -1
- package/lib/test/objects.js +3 -1
- package/lib/test/objects.js.map +1 -1
- package/package.json +12 -11
- package/webda.module.json +135 -41
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { TransformCaseType } from "@webda/utils";
|
|
2
2
|
import { OperationsTransport, OperationsTransportParameters } from "../services/operationstransport.js";
|
|
3
3
|
import { OperationDefinition } from "../core/icore.js";
|
|
4
|
+
import { useInstanceStorage } from "../core/instancestorage.js";
|
|
4
5
|
import { WebContext } from "../contexts/webcontext.js";
|
|
6
|
+
import type { IncomingMessage, Server, ServerResponse } from "node:http";
|
|
7
|
+
import type { Duplex } from "node:stream";
|
|
8
|
+
import { type WebSocket } from "ws";
|
|
5
9
|
import type { ModelClass } from "@webda/models";
|
|
6
10
|
import type { ModelAction } from "../models/types.js";
|
|
7
11
|
import type { ModelGraphBehaviorDefinition, ModelMetadata } from "@webda/compiler";
|
|
@@ -42,6 +46,31 @@ export declare class RESTOperationsTransportParameters extends OperationsTranspo
|
|
|
42
46
|
* @default "PUT"
|
|
43
47
|
*/
|
|
44
48
|
queryMethod?: "PUT" | "GET";
|
|
49
|
+
/**
|
|
50
|
+
* Largest message (bytes) a client may send on a bidirectional WebSocket operation; a bigger one closes the socket
|
|
51
|
+
*
|
|
52
|
+
* @default 1048576
|
|
53
|
+
*/
|
|
54
|
+
webSocketMaxPayload?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Most unconsumed messages a bidirectional operation may have queued; above it the socket closes with 4413
|
|
57
|
+
*
|
|
58
|
+
* @default 1000
|
|
59
|
+
*/
|
|
60
|
+
webSocketMaxQueuedMessages?: number;
|
|
61
|
+
/**
|
|
62
|
+
* Most bytes of unconsumed messages a bidirectional operation may have queued; above it the socket closes with 4413
|
|
63
|
+
*
|
|
64
|
+
* @default 16777216
|
|
65
|
+
*/
|
|
66
|
+
webSocketMaxQueuedBytes?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Milliseconds between two keep-alive comments on an open `text/event-stream` response of a server-streaming
|
|
69
|
+
* operation (0 disables them)
|
|
70
|
+
*
|
|
71
|
+
* @default 20000
|
|
72
|
+
*/
|
|
73
|
+
streamKeepAliveInterval?: number;
|
|
45
74
|
/**
|
|
46
75
|
* Load parameters with defaults
|
|
47
76
|
* @param params - the service parameters
|
|
@@ -49,6 +78,13 @@ export declare class RESTOperationsTransportParameters extends OperationsTranspo
|
|
|
49
78
|
*/
|
|
50
79
|
load(params?: any): this;
|
|
51
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Truncate a string to a number of UTF-8 bytes without cutting a character (a close reason holds 123 bytes)
|
|
83
|
+
* @param text - the text
|
|
84
|
+
* @param max - the maximum byte length
|
|
85
|
+
* @returns the truncated text
|
|
86
|
+
*/
|
|
87
|
+
export declare function truncateUtf8(text: string, max: number): string;
|
|
52
88
|
/**
|
|
53
89
|
* REST transport that exposes model operations as RESTful HTTP routes.
|
|
54
90
|
*
|
|
@@ -63,6 +99,13 @@ export declare class RESTOperationsTransport<T extends RESTOperationsTransportPa
|
|
|
63
99
|
* OpenAPI cache
|
|
64
100
|
*/
|
|
65
101
|
openapiContent: string;
|
|
102
|
+
/** Bidirectional operations served as WebSocket upgrades: path → operation id */
|
|
103
|
+
webSocketRoutes: Map<string, string>;
|
|
104
|
+
/** Shared WebSocket server (noServer: upgrades come from the HttpServer) */
|
|
105
|
+
private webSocketServer?;
|
|
106
|
+
/** HTTP servers the upgrade listener is already attached to */
|
|
107
|
+
private webSocketServers;
|
|
108
|
+
private webSocketEvents;
|
|
66
109
|
/**
|
|
67
110
|
* Transform name using configured casing
|
|
68
111
|
* @param name - the name to transform
|
|
@@ -74,6 +117,45 @@ export declare class RESTOperationsTransport<T extends RESTOperationsTransportPa
|
|
|
74
117
|
* @returns this for chaining
|
|
75
118
|
*/
|
|
76
119
|
resolve(): this;
|
|
120
|
+
/**
|
|
121
|
+
* Find the bidirectional operation served on a request URL, the way the router matches HTTP paths: relative to the
|
|
122
|
+
* HttpContext prefix, without the query string, with or without the global route prefix
|
|
123
|
+
* @param uri - the request URL
|
|
124
|
+
* @param prefix - the HttpContext prefix (a gateway stage...)
|
|
125
|
+
* @returns the operation id, if any
|
|
126
|
+
*/
|
|
127
|
+
webSocketOperationOf(uri: string, prefix?: string): string | undefined;
|
|
128
|
+
/**
|
|
129
|
+
* Serve the bidirectional operations' upgrades on an HTTP server (once per server, only if there are any)
|
|
130
|
+
* @param server - the server emitted by Webda.Init.Http
|
|
131
|
+
* @param storage - the instance storage to run requests in
|
|
132
|
+
*/
|
|
133
|
+
protected attachWebSockets(server: Server, storage: ReturnType<typeof useInstanceStorage>): void;
|
|
134
|
+
/**
|
|
135
|
+
* Answer an upgrade with an HTTP error
|
|
136
|
+
* @param socket - the raw socket
|
|
137
|
+
* @param status - HTTP status
|
|
138
|
+
* @param message - reason phrase
|
|
139
|
+
*/
|
|
140
|
+
private refuseUpgrade;
|
|
141
|
+
/**
|
|
142
|
+
* Check the caller like a normal request (Webda.Request, router request filters, permission) and accept the WebSocket
|
|
143
|
+
* @param opId - the bidirectional operation
|
|
144
|
+
* @param server - the HTTP server
|
|
145
|
+
* @param req - the upgrade request
|
|
146
|
+
* @param socket - the raw socket
|
|
147
|
+
* @param head - first packet of the upgraded stream
|
|
148
|
+
* @returns a promise settled once the upgrade is accepted or refused
|
|
149
|
+
*/
|
|
150
|
+
protected upgradeOperation(opId: string, server: Server, req: IncomingMessage, socket: Duplex, head: Buffer): Promise<void>;
|
|
151
|
+
/**
|
|
152
|
+
* Run a bidirectional operation over an accepted WebSocket: messages in, chunks out
|
|
153
|
+
* @param opId - the operation
|
|
154
|
+
* @param httpContext - the upgrade request
|
|
155
|
+
* @param ws - the socket
|
|
156
|
+
* @param session - the session loaded when the upgrade was authorized
|
|
157
|
+
*/
|
|
158
|
+
protected runWebSocketOperation(opId: string, httpContext: any, ws: WebSocket, session?: any): Promise<void>;
|
|
77
159
|
/**
|
|
78
160
|
* Override initTransport to walk the model tree instead of flat iteration.
|
|
79
161
|
* For each model, we look up its operations and register REST routes.
|
|
@@ -85,6 +167,32 @@ export declare class RESTOperationsTransport<T extends RESTOperationsTransportPa
|
|
|
85
167
|
* @param operations - the filtered operations map
|
|
86
168
|
*/
|
|
87
169
|
protected exposeServiceOperations(operations: Record<string, OperationDefinition>): void;
|
|
170
|
+
/**
|
|
171
|
+
* The OpenAPI responses of a server-streaming operation
|
|
172
|
+
* @returns the 200 response with both stream formats
|
|
173
|
+
*/
|
|
174
|
+
protected streamedResponses(): Record<string, any>;
|
|
175
|
+
/**
|
|
176
|
+
* Run an operation for a route: a server-streaming one is streamed to the client as its generator yields, any other
|
|
177
|
+
* is called and its result flushed with the response
|
|
178
|
+
* @param context - the request context
|
|
179
|
+
* @param operationId - the operation
|
|
180
|
+
* @returns a promise settled once the operation is done (a stream: once the response is over)
|
|
181
|
+
*/
|
|
182
|
+
protected runOperation(context: WebContext, operationId: string): Promise<void>;
|
|
183
|
+
/**
|
|
184
|
+
* Stream a server-streaming operation: NDJSON, or server-sent events when the client accepts them
|
|
185
|
+
*
|
|
186
|
+
* The streaming context is built here, from the request's context, rather than by the HttpServer: only these routes
|
|
187
|
+
* need it, and permissions, validation and events still go through `callOperation` on the request's input. Until the
|
|
188
|
+
* first chunk an error is a normal HTTP error (thrown to the HttpServer); after it, the error is the last event or
|
|
189
|
+
* line of the stream.
|
|
190
|
+
* @param context - the request context
|
|
191
|
+
* @param operationId - the operation
|
|
192
|
+
* @param response - the HTTP response
|
|
193
|
+
* @returns a promise settled once the response is over
|
|
194
|
+
*/
|
|
195
|
+
protected streamOperation(context: WebContext, operationId: string, response: ServerResponse): Promise<void>;
|
|
88
196
|
/**
|
|
89
197
|
* Recursively walk a model and its children, registering routes at each level
|
|
90
198
|
* @param model - the model class
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"restoperationstransport.service.d.ts","sourceRoot":"","sources":["../../src/rest/restoperationstransport.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AACxG,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"restoperationstransport.service.d.ts","sourceRoot":"","sources":["../../src/rest/restoperationstransport.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AACxG,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAUvD,OAAO,EAA0B,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAGxF,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAGvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACzE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,IAAI,CAAC;AAErD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,4BAA4B,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAsDnF;;GAEG;AACH,qBAAa,iCAAkC,SAAQ,6BAA6B;IAClF;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;OAQG;IACH,cAAc,EAAE,MAAM,CAAY;IAClC;;OAEG;IACH,GAAG,EAAE,MAAM,CAAO;IAClB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,iBAAiB,CAAC;IACpC;;;;OAIG;IACH,WAAW,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAC5B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC;;;;;OAKG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAEjC;;;;OAIG;IACH,IAAI,CAAC,MAAM,GAAE,GAAQ,GAAG,IAAI,CAa3B;CACF;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAM9D;AAED;;;;;;;;GAQG;AACH,qBAAa,uBAAuB,CAClC,CAAC,SAAS,iCAAiC,GAAG,iCAAiC,CAC/E,SAAQ,mBAAmB,CAAC,CAAC,CAAC;IAC9B;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IAEvB,iFAAiF;IACjF,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAa;IACjD,4EAA4E;IAC5E,OAAO,CAAC,eAAe,CAAC,CAAkB;IAC1C,+DAA+D;IAC/D,OAAO,CAAC,gBAAgB,CAAkC;IAC1D,OAAO,CAAC,eAAe,CAAS;IAEhC;;;;OAIG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElC;IAED;;;OAGG;IACH,OAAO,SAaN;IAED;;;;;;OAMG;IACH,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,MAAW,GAAG,MAAM,GAAG,SAAS,CAOzE;IAED;;;;OAIG;IACH,SAAS,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,GAAG,IAAI,CAc/F;IAED;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAIrB;;;;;;;;OAQG;IACH,UAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CA6Bf;IAED;;;;;;OAMG;IACH,UAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CA+CjH;IAED;;;OAGG;IACH,SAAS,CAAC,aAAa,IAAI,IAAI,CAiC9B;IAED;;;;OAIG;IACH,SAAS,CAAC,uBAAuB,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,GAAG,IAAI,CAwDvF;IAED;;;OAGG;IACH,SAAS,CAAC,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAajD;IAED;;;;;;OAMG;IACH,UAAgB,YAAY,CAAC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASpF;IAED;;;;;;;;;;;OAWG;IACH,UAAgB,eAAe,CAAC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA8CjH;IAED;;;;;;;OAOG;IACH,SAAS,CAAC,SAAS,CACjB,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,aAAa,EACvB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAC/C,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,GACZ,IAAI,CA+CN;IAED;;;;;;;;;OASG;IACH,SAAS,CAAC,gBAAgB,CACxB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,MAAM,GAAG,SAAS,EACnC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,GAC9C,IAAI,CAqGN;IAED;;;;;;;;OAQG;IACH,SAAS,CAAC,iBAAiB,CACzB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,MAAM,GAAG,SAAS,EACnC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,GAC9C,IAAI,CA2EN;IAED;;;;;;OAMG;IACH,SAAS,CAAC,iBAAiB,CACzB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,GAC9C,IAAI,CA2CN;IAED;;;;;;OAMG;IACH,SAAS,CAAC,iBAAiB,CACzB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,GAC9C,IAAI,CAqEN;IAED;;;;;;OAMG;IACH,SAAS,CAAC,cAAc,CACtB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,GAC9C,IAAI,CA2CN;IAED;;;;;;;;;OASG;IACH,SAAS,CAAC,iBAAiB,CACzB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,MAAM,GAAG,SAAS,GAClC,IAAI,CAmEN;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,SAAS,CAAC,oBAAoB,CAC5B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,4BAA4B,GACrC,IAAI,CAsEN;IAED;;;;OAIG;IACH,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,mBAAmB,GAAG,IAAI,CAE5E;IAED;;;OAGG;IACG,OAAO,CAAC,GAAG,EAAE,UAAU,iBAM5B;CACF"}
|
|
@@ -3,11 +3,28 @@ import { TransformCase } from "@webda/utils";
|
|
|
3
3
|
import { OperationsTransport, OperationsTransportParameters } from "../services/operationstransport.js";
|
|
4
4
|
import * as WebdaError from "../errors/errors.js";
|
|
5
5
|
import { useRouter } from "./hooks.js";
|
|
6
|
-
import {
|
|
6
|
+
import { useLog } from "@webda/workout";
|
|
7
|
+
import { HttpContext } from "../contexts/httpcontext.js";
|
|
8
|
+
import { runWithContext } from "../contexts/execution.js";
|
|
9
|
+
import { emitCoreEvent, useCoreEvents } from "../events/events.js";
|
|
10
|
+
import { useApplication, useParameters } from "../application/hooks.js";
|
|
7
11
|
import { useCore, useModelMetadata } from "../core/hooks.js";
|
|
8
|
-
import { useInstanceStorage } from "../core/instancestorage.js";
|
|
9
|
-
import { callOperation } from "../core/operations.js";
|
|
12
|
+
import { runWithInstanceStorage, useInstanceStorage } from "../core/instancestorage.js";
|
|
13
|
+
import { callOperation, canCallOperation, getOperationStreaming } from "../core/operations.js";
|
|
14
|
+
import { AsyncQueue } from "../core/asyncqueue.js";
|
|
15
|
+
import { WebSocketOperationContext } from "../contexts/websocketcontext.js";
|
|
16
|
+
import { RestStreamingOperationContext } from "../contexts/restcontext.js";
|
|
17
|
+
import { WebSocketServer } from "ws";
|
|
10
18
|
import { hasSchema } from "../schemas/hooks.js";
|
|
19
|
+
/**
|
|
20
|
+
* @param data - a raw WebSocket message
|
|
21
|
+
* @returns its length in bytes
|
|
22
|
+
*/
|
|
23
|
+
function rawLength(data) {
|
|
24
|
+
if (Array.isArray(data))
|
|
25
|
+
return data.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
26
|
+
return data instanceof ArrayBuffer ? data.byteLength : data.length;
|
|
27
|
+
}
|
|
11
28
|
/**
|
|
12
29
|
* Swagger static html
|
|
13
30
|
*/
|
|
@@ -87,6 +104,31 @@ export class RESTOperationsTransportParameters extends OperationsTransportParame
|
|
|
87
104
|
* @default "PUT"
|
|
88
105
|
*/
|
|
89
106
|
queryMethod;
|
|
107
|
+
/**
|
|
108
|
+
* Largest message (bytes) a client may send on a bidirectional WebSocket operation; a bigger one closes the socket
|
|
109
|
+
*
|
|
110
|
+
* @default 1048576
|
|
111
|
+
*/
|
|
112
|
+
webSocketMaxPayload;
|
|
113
|
+
/**
|
|
114
|
+
* Most unconsumed messages a bidirectional operation may have queued; above it the socket closes with 4413
|
|
115
|
+
*
|
|
116
|
+
* @default 1000
|
|
117
|
+
*/
|
|
118
|
+
webSocketMaxQueuedMessages;
|
|
119
|
+
/**
|
|
120
|
+
* Most bytes of unconsumed messages a bidirectional operation may have queued; above it the socket closes with 4413
|
|
121
|
+
*
|
|
122
|
+
* @default 16777216
|
|
123
|
+
*/
|
|
124
|
+
webSocketMaxQueuedBytes;
|
|
125
|
+
/**
|
|
126
|
+
* Milliseconds between two keep-alive comments on an open `text/event-stream` response of a server-streaming
|
|
127
|
+
* operation (0 disables them)
|
|
128
|
+
*
|
|
129
|
+
* @default 20000
|
|
130
|
+
*/
|
|
131
|
+
streamKeepAliveInterval;
|
|
90
132
|
/**
|
|
91
133
|
* Load parameters with defaults
|
|
92
134
|
* @param params - the service parameters
|
|
@@ -96,6 +138,10 @@ export class RESTOperationsTransportParameters extends OperationsTransportParame
|
|
|
96
138
|
super.load(params);
|
|
97
139
|
this.nameTransformer ??= "camelCase";
|
|
98
140
|
this.queryMethod ??= "PUT";
|
|
141
|
+
this.webSocketMaxPayload ??= 1024 * 1024;
|
|
142
|
+
this.webSocketMaxQueuedMessages ??= 1000;
|
|
143
|
+
this.webSocketMaxQueuedBytes ??= 16 * 1024 * 1024;
|
|
144
|
+
this.streamKeepAliveInterval ??= 20000;
|
|
99
145
|
// Ensure url ends with /
|
|
100
146
|
if (this.url && !this.url.endsWith("/")) {
|
|
101
147
|
this.url += "/";
|
|
@@ -103,6 +149,21 @@ export class RESTOperationsTransportParameters extends OperationsTransportParame
|
|
|
103
149
|
return this;
|
|
104
150
|
}
|
|
105
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Truncate a string to a number of UTF-8 bytes without cutting a character (a close reason holds 123 bytes)
|
|
154
|
+
* @param text - the text
|
|
155
|
+
* @param max - the maximum byte length
|
|
156
|
+
* @returns the truncated text
|
|
157
|
+
*/
|
|
158
|
+
export function truncateUtf8(text, max) {
|
|
159
|
+
const buffer = Buffer.from(text, "utf8");
|
|
160
|
+
if (buffer.length <= max)
|
|
161
|
+
return text;
|
|
162
|
+
let end = max;
|
|
163
|
+
while (end > 0 && (buffer[end] & 0xc0) === 0x80)
|
|
164
|
+
end--;
|
|
165
|
+
return buffer.subarray(0, end).toString("utf8");
|
|
166
|
+
}
|
|
106
167
|
/**
|
|
107
168
|
* REST transport that exposes model operations as RESTful HTTP routes.
|
|
108
169
|
*
|
|
@@ -117,6 +178,13 @@ export class RESTOperationsTransport extends OperationsTransport {
|
|
|
117
178
|
* OpenAPI cache
|
|
118
179
|
*/
|
|
119
180
|
openapiContent;
|
|
181
|
+
/** Bidirectional operations served as WebSocket upgrades: path → operation id */
|
|
182
|
+
webSocketRoutes = new Map();
|
|
183
|
+
/** Shared WebSocket server (noServer: upgrades come from the HttpServer) */
|
|
184
|
+
webSocketServer;
|
|
185
|
+
/** HTTP servers the upgrade listener is already attached to */
|
|
186
|
+
webSocketServers = new WeakSet();
|
|
187
|
+
webSocketEvents = false;
|
|
120
188
|
/**
|
|
121
189
|
* Transform name using configured casing
|
|
122
190
|
* @param name - the name to transform
|
|
@@ -135,8 +203,162 @@ export class RESTOperationsTransport extends OperationsTransport {
|
|
|
135
203
|
if (this.parameters.exposeOpenAPI) {
|
|
136
204
|
this.addRoute(".", ["GET"], this.openapi, { hidden: true });
|
|
137
205
|
}
|
|
206
|
+
// Bidirectional operations: accept WebSocket upgrades on every HTTP server once it listens
|
|
207
|
+
if (!this.webSocketEvents) {
|
|
208
|
+
this.webSocketEvents = true;
|
|
209
|
+
const storage = useInstanceStorage();
|
|
210
|
+
useCoreEvents("Webda.Init.Http", (server) => this.attachWebSockets(server, storage));
|
|
211
|
+
}
|
|
138
212
|
return this;
|
|
139
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Find the bidirectional operation served on a request URL, the way the router matches HTTP paths: relative to the
|
|
216
|
+
* HttpContext prefix, without the query string, with or without the global route prefix
|
|
217
|
+
* @param uri - the request URL
|
|
218
|
+
* @param prefix - the HttpContext prefix (a gateway stage...)
|
|
219
|
+
* @returns the operation id, if any
|
|
220
|
+
*/
|
|
221
|
+
webSocketOperationOf(uri, prefix = "") {
|
|
222
|
+
const context = new HttpContext("localhost", "GET", uri);
|
|
223
|
+
if (prefix)
|
|
224
|
+
context.setPrefix(prefix);
|
|
225
|
+
let path = context.getRelativeUri().split("?")[0];
|
|
226
|
+
const routePrefix = useParameters().routePrefix || "";
|
|
227
|
+
if (routePrefix && path.startsWith(routePrefix))
|
|
228
|
+
path = path.substring(routePrefix.length) || "/";
|
|
229
|
+
return this.webSocketRoutes.get(path);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Serve the bidirectional operations' upgrades on an HTTP server (once per server, only if there are any)
|
|
233
|
+
* @param server - the server emitted by Webda.Init.Http
|
|
234
|
+
* @param storage - the instance storage to run requests in
|
|
235
|
+
*/
|
|
236
|
+
attachWebSockets(server, storage) {
|
|
237
|
+
if (this.webSocketRoutes.size === 0 || this.webSocketServers.has(server))
|
|
238
|
+
return;
|
|
239
|
+
this.webSocketServers.add(server);
|
|
240
|
+
server.on("upgrade", (req, socket, head) => {
|
|
241
|
+
runWithInstanceStorage(storage, () => {
|
|
242
|
+
const opId = this.webSocketOperationOf(req.url ?? "/");
|
|
243
|
+
// Not ours (GraphQL subscriptions...): leave the socket to the other listeners
|
|
244
|
+
if (!opId)
|
|
245
|
+
return;
|
|
246
|
+
this.upgradeOperation(opId, server, req, socket, head).catch(err => {
|
|
247
|
+
useLog("ERROR", "WebSocket upgrade failed", opId, err);
|
|
248
|
+
socket.destroy();
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Answer an upgrade with an HTTP error
|
|
255
|
+
* @param socket - the raw socket
|
|
256
|
+
* @param status - HTTP status
|
|
257
|
+
* @param message - reason phrase
|
|
258
|
+
*/
|
|
259
|
+
refuseUpgrade(socket, status, message) {
|
|
260
|
+
socket.end(`HTTP/1.1 ${status} ${message}\r\nConnection: close\r\nContent-Length: 0\r\n\r\n`);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Check the caller like a normal request (Webda.Request, router request filters, permission) and accept the WebSocket
|
|
264
|
+
* @param opId - the bidirectional operation
|
|
265
|
+
* @param server - the HTTP server
|
|
266
|
+
* @param req - the upgrade request
|
|
267
|
+
* @param socket - the raw socket
|
|
268
|
+
* @param head - first packet of the upgraded stream
|
|
269
|
+
* @returns a promise settled once the upgrade is accepted or refused
|
|
270
|
+
*/
|
|
271
|
+
async upgradeOperation(opId, server, req, socket, head) {
|
|
272
|
+
const http = Object.values(useCore().getServices()).find(s => s.server === server && typeof s.getContextFromRequest === "function");
|
|
273
|
+
const base = http && (await http.getContextFromRequest(req));
|
|
274
|
+
if (!base)
|
|
275
|
+
return this.refuseUpgrade(socket, 400, "Bad Request");
|
|
276
|
+
await base.init();
|
|
277
|
+
const allowed = await runWithContext(base, async () => {
|
|
278
|
+
try {
|
|
279
|
+
emitCoreEvent("Webda.Request", { context: base });
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
// listener error
|
|
283
|
+
}
|
|
284
|
+
return (await useRouter().checkRequest(base)) && canCallOperation(base, opId);
|
|
285
|
+
});
|
|
286
|
+
if (!allowed)
|
|
287
|
+
return this.refuseUpgrade(socket, 403, "Forbidden");
|
|
288
|
+
const storage = useInstanceStorage();
|
|
289
|
+
this.webSocketServer ??= new WebSocketServer({
|
|
290
|
+
noServer: true,
|
|
291
|
+
maxPayload: this.parameters.webSocketMaxPayload
|
|
292
|
+
});
|
|
293
|
+
this.webSocketServer.handleUpgrade(req, socket, head, ws => {
|
|
294
|
+
runWithInstanceStorage(storage, () => {
|
|
295
|
+
this.runWebSocketOperation(opId, base.getHttpContext(), ws, base.getSession()).catch(err => {
|
|
296
|
+
useLog("ERROR", "WebSocket operation failed", opId, err);
|
|
297
|
+
ws.close(1011, "Internal server error");
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Run a bidirectional operation over an accepted WebSocket: messages in, chunks out
|
|
304
|
+
* @param opId - the operation
|
|
305
|
+
* @param httpContext - the upgrade request
|
|
306
|
+
* @param ws - the socket
|
|
307
|
+
* @param session - the session loaded when the upgrade was authorized
|
|
308
|
+
*/
|
|
309
|
+
async runWebSocketOperation(opId, httpContext, ws, session) {
|
|
310
|
+
const ctx = new WebSocketOperationContext(httpContext, ws, session);
|
|
311
|
+
await ctx.init();
|
|
312
|
+
const input = new AsyncQueue();
|
|
313
|
+
ctx.setExtension("operationInputStream", input);
|
|
314
|
+
const maxQueued = this.parameters.webSocketMaxQueuedMessages;
|
|
315
|
+
const maxQueuedBytes = this.parameters.webSocketMaxQueuedBytes;
|
|
316
|
+
ws.on("message", data => {
|
|
317
|
+
let message;
|
|
318
|
+
try {
|
|
319
|
+
message = JSON.parse(String(data));
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
ws.close(4400, "Invalid JSON message");
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
input.push(message, rawLength(data));
|
|
326
|
+
if (input.pending > maxQueued || input.pendingBytes > maxQueuedBytes) {
|
|
327
|
+
// The operation does not keep up: stop it rather than buffering without limit
|
|
328
|
+
const bytes = input.pendingBytes > maxQueuedBytes;
|
|
329
|
+
ctx.cancel();
|
|
330
|
+
input.end(true);
|
|
331
|
+
ws.close(4413, bytes ? "Too many queued bytes" : "Too many queued messages");
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
ws.on("error", err => useLog("DEBUG", "WebSocket error", opId, err));
|
|
335
|
+
// The client is gone: the operation's writes throw and its pending read ends so its finally blocks run
|
|
336
|
+
ws.on("close", () => {
|
|
337
|
+
ctx.cancel();
|
|
338
|
+
input.end();
|
|
339
|
+
});
|
|
340
|
+
try {
|
|
341
|
+
await callOperation(ctx, opId);
|
|
342
|
+
if (ctx.isCancelled)
|
|
343
|
+
return;
|
|
344
|
+
const output = ctx.getExtension("operationStreaming") ? undefined : ctx.getOutput();
|
|
345
|
+
if (output)
|
|
346
|
+
ws.send(output);
|
|
347
|
+
ws.close(1000);
|
|
348
|
+
}
|
|
349
|
+
catch (err) {
|
|
350
|
+
// Only a client that went away is a silent cancel: an operation raising it while connected is an error
|
|
351
|
+
if (ctx.isCancelled)
|
|
352
|
+
return;
|
|
353
|
+
const status = typeof err?.getResponseCode === "function" ? err.getResponseCode() : undefined;
|
|
354
|
+
if (status >= 400 && status < 500) {
|
|
355
|
+
ws.close(4000 + status, truncateUtf8(String(err.message ?? "Error"), 123));
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
useLog("ERROR", `[WebSocket ${opId}] operation threw:`, err);
|
|
359
|
+
ws.close(status ? 4000 + status : 1011, "Internal server error");
|
|
360
|
+
}
|
|
361
|
+
}
|
|
140
362
|
/**
|
|
141
363
|
* Override initTransport to walk the model tree instead of flat iteration.
|
|
142
364
|
* For each model, we look up its operations and register REST routes.
|
|
@@ -187,6 +409,9 @@ export class RESTOperationsTransport extends OperationsTransport {
|
|
|
187
409
|
for (const [opId, op] of Object.entries(operations)) {
|
|
188
410
|
if (op.hidden)
|
|
189
411
|
continue;
|
|
412
|
+
// A client stream cannot be carried by a plain HTTP request: reachable over gRPC only
|
|
413
|
+
if (getOperationStreaming(op) === "client")
|
|
414
|
+
continue;
|
|
190
415
|
// Skip if this operation was already handled by model tree walk
|
|
191
416
|
if (op.context?.model)
|
|
192
417
|
continue;
|
|
@@ -202,18 +427,121 @@ export class RESTOperationsTransport extends OperationsTransport {
|
|
|
202
427
|
path = `${this.parameters.url}${opId.toLowerCase().replace(/\./g, "/")}`;
|
|
203
428
|
methods = ["PUT"];
|
|
204
429
|
}
|
|
430
|
+
if (getOperationStreaming(op) === "bidi") {
|
|
431
|
+
// Served as a WebSocket upgrade on this path; a plain request cannot carry a bidirectional stream
|
|
432
|
+
if (path.includes("{")) {
|
|
433
|
+
this.log("WARN", `${opId} is bidirectional but its path ${path} has parameters: not served over WebSocket`);
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
this.webSocketRoutes.set(path, opId);
|
|
437
|
+
}
|
|
438
|
+
this.addRoute(path, [...new Set(["GET", ...methods])], async () => {
|
|
439
|
+
throw new WebdaError.HttpError("Upgrade Required: open a WebSocket on this URL", 426);
|
|
440
|
+
}, { get: { tags: op.tags || [], summary: `${op.summary || opId} (WebSocket)`, operationId: opId } });
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
205
443
|
const openapi = {
|
|
206
444
|
[methods[0].toLowerCase()]: {
|
|
207
445
|
tags: op.tags || [],
|
|
208
446
|
summary: op.summary || opId,
|
|
209
|
-
operationId: opId
|
|
447
|
+
operationId: opId,
|
|
448
|
+
...(getOperationStreaming(op) === "server" ? { responses: this.streamedResponses() } : {})
|
|
210
449
|
}
|
|
211
450
|
};
|
|
212
451
|
this.addRoute(path, methods, async (context) => {
|
|
213
|
-
await
|
|
452
|
+
await this.runOperation(context, opId);
|
|
214
453
|
}, openapi);
|
|
215
454
|
}
|
|
216
455
|
}
|
|
456
|
+
/**
|
|
457
|
+
* The OpenAPI responses of a server-streaming operation
|
|
458
|
+
* @returns the 200 response with both stream formats
|
|
459
|
+
*/
|
|
460
|
+
streamedResponses() {
|
|
461
|
+
return {
|
|
462
|
+
"200": {
|
|
463
|
+
description: "Stream of chunks: one JSON line each (application/x-ndjson), or one `data:` event each when `Accept` is " +
|
|
464
|
+
"text/event-stream, ended by an `end` event. An error after the first chunk is a last `error` event or " +
|
|
465
|
+
'`{"error": {"message", "code"}}` line.',
|
|
466
|
+
content: {
|
|
467
|
+
"application/x-ndjson": { schema: { type: "string", description: "One JSON value per line" } },
|
|
468
|
+
"text/event-stream": { schema: { type: "string", description: "Server-sent events with a JSON `data`" } }
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Run an operation for a route: a server-streaming one is streamed to the client as its generator yields, any other
|
|
475
|
+
* is called and its result flushed with the response
|
|
476
|
+
* @param context - the request context
|
|
477
|
+
* @param operationId - the operation
|
|
478
|
+
* @returns a promise settled once the operation is done (a stream: once the response is over)
|
|
479
|
+
*/
|
|
480
|
+
async runOperation(context, operationId) {
|
|
481
|
+
const response = context._stream;
|
|
482
|
+
if (getOperationStreaming(useInstanceStorage().operations?.[operationId]) !== "server" ||
|
|
483
|
+
typeof response?.writeHead !== "function") {
|
|
484
|
+
return callOperation(context, operationId);
|
|
485
|
+
}
|
|
486
|
+
return this.streamOperation(context, operationId, response);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Stream a server-streaming operation: NDJSON, or server-sent events when the client accepts them
|
|
490
|
+
*
|
|
491
|
+
* The streaming context is built here, from the request's context, rather than by the HttpServer: only these routes
|
|
492
|
+
* need it, and permissions, validation and events still go through `callOperation` on the request's input. Until the
|
|
493
|
+
* first chunk an error is a normal HTTP error (thrown to the HttpServer); after it, the error is the last event or
|
|
494
|
+
* line of the stream.
|
|
495
|
+
* @param context - the request context
|
|
496
|
+
* @param operationId - the operation
|
|
497
|
+
* @param response - the HTTP response
|
|
498
|
+
* @returns a promise settled once the response is over
|
|
499
|
+
*/
|
|
500
|
+
async streamOperation(context, operationId, response) {
|
|
501
|
+
const accept = context.getHttpContext().getUniqueHeader("accept", "") ?? "";
|
|
502
|
+
const format = accept.includes("text/event-stream") ? "sse" : "ndjson";
|
|
503
|
+
const stream = new RestStreamingOperationContext(context.getHttpContext(), response, format, context.getSession(), this.parameters.streamKeepAliveInterval, context.getResponseHeaders(), context.getSetCookieHeaders());
|
|
504
|
+
await stream.init();
|
|
505
|
+
stream.setParameters(context.getParameters());
|
|
506
|
+
stream.startKeepAlive();
|
|
507
|
+
try {
|
|
508
|
+
await callOperation(stream, operationId);
|
|
509
|
+
}
|
|
510
|
+
catch (err) {
|
|
511
|
+
stream.stopKeepAlive();
|
|
512
|
+
// The client went away: nothing to tell
|
|
513
|
+
if (stream.isCancelled || stream.disconnected)
|
|
514
|
+
return stream.finish();
|
|
515
|
+
// Nothing sent yet: a normal HTTP error response
|
|
516
|
+
if (!stream.hasStarted) {
|
|
517
|
+
stream.abortBeforeStart();
|
|
518
|
+
throw err;
|
|
519
|
+
}
|
|
520
|
+
const status = typeof err?.getResponseCode === "function" ? err.getResponseCode() : undefined;
|
|
521
|
+
const clientError = status >= 400 && status < 500;
|
|
522
|
+
if (!clientError)
|
|
523
|
+
useLog("ERROR", `[REST ${operationId}] streamed operation threw:`, err);
|
|
524
|
+
await stream.writeError(clientError ? String(err.message ?? "Error") : "Internal server error", status || 500);
|
|
525
|
+
return stream.finish();
|
|
526
|
+
}
|
|
527
|
+
stream.stopKeepAlive();
|
|
528
|
+
if (stream.isCancelled)
|
|
529
|
+
return stream.finish();
|
|
530
|
+
if (!stream.getExtension("operationStreaming")) {
|
|
531
|
+
// Not a generator after all: a normal response, with the status and headers the operation set
|
|
532
|
+
if (stream.statusCode && stream.statusCode !== 204)
|
|
533
|
+
context.statusCode = stream.statusCode;
|
|
534
|
+
for (const [name, value] of Object.entries(stream.getResponseHeaders()))
|
|
535
|
+
context.setHeader(name, value);
|
|
536
|
+
const output = stream.getOutput();
|
|
537
|
+
if (output !== undefined) {
|
|
538
|
+
context.setHeader("Content-type", "application/json");
|
|
539
|
+
context.write(output);
|
|
540
|
+
}
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
return stream.finish(true);
|
|
544
|
+
}
|
|
217
545
|
/**
|
|
218
546
|
* Recursively walk a model and its children, registering routes at each level
|
|
219
547
|
* @param model - the model class
|
|
@@ -637,6 +965,13 @@ export class RESTOperationsTransport extends OperationsTransport {
|
|
|
637
965
|
...(action.openapi?.[method.toLowerCase()] ?? {})
|
|
638
966
|
};
|
|
639
967
|
});
|
|
968
|
+
if (getOperationStreaming(useInstanceStorage().operations?.[operationId]) === "server") {
|
|
969
|
+
Object.keys(openapi)
|
|
970
|
+
.filter(k => ["get", "post", "put", "patch", "delete"].includes(k))
|
|
971
|
+
.forEach(k => {
|
|
972
|
+
openapi[k].responses = { ...this.streamedResponses(), ...openapi[k].responses };
|
|
973
|
+
});
|
|
974
|
+
}
|
|
640
975
|
if (hasSchema(`${identifier}.${actionName}.input`)) {
|
|
641
976
|
Object.keys(openapi)
|
|
642
977
|
.filter(k => ["get", "post", "put", "patch", "delete"].includes(k))
|
|
@@ -652,7 +987,8 @@ export class RESTOperationsTransport extends OperationsTransport {
|
|
|
652
987
|
};
|
|
653
988
|
});
|
|
654
989
|
}
|
|
655
|
-
if (hasSchema(`${identifier}.${actionName}.output`)
|
|
990
|
+
if (hasSchema(`${identifier}.${actionName}.output`) &&
|
|
991
|
+
getOperationStreaming(useInstanceStorage().operations?.[operationId]) !== "server") {
|
|
656
992
|
Object.keys(openapi)
|
|
657
993
|
.filter(k => ["get", "post", "put", "patch", "delete"].includes(k))
|
|
658
994
|
.forEach(k => {
|
|
@@ -672,7 +1008,7 @@ export class RESTOperationsTransport extends OperationsTransport {
|
|
|
672
1008
|
context.getParameters()[injectAttribute] = context.parameter(`pid.${depth - 1}`);
|
|
673
1009
|
context.getParameters()[`pid.${depth - 1}`] = undefined;
|
|
674
1010
|
}
|
|
675
|
-
await
|
|
1011
|
+
await this.runOperation(context, operationId);
|
|
676
1012
|
}, openapi);
|
|
677
1013
|
}
|
|
678
1014
|
/**
|
|
@@ -745,7 +1081,10 @@ export class RESTOperationsTransport extends OperationsTransport {
|
|
|
745
1081
|
}
|
|
746
1082
|
};
|
|
747
1083
|
}
|
|
748
|
-
if (
|
|
1084
|
+
if (getOperationStreaming(op) === "server") {
|
|
1085
|
+
openapi[methodKey].responses = this.streamedResponses();
|
|
1086
|
+
}
|
|
1087
|
+
else if (hasSchema(outputSchema)) {
|
|
749
1088
|
openapi[methodKey].responses = {
|
|
750
1089
|
"200": {
|
|
751
1090
|
description: "Operation success",
|
|
@@ -759,7 +1098,7 @@ export class RESTOperationsTransport extends OperationsTransport {
|
|
|
759
1098
|
}
|
|
760
1099
|
};
|
|
761
1100
|
}
|
|
762
|
-
this.addRoute(fullPath, [httpMethod], async (context) =>
|
|
1101
|
+
this.addRoute(fullPath, [httpMethod], async (context) => this.runOperation(context, operationId), openapi);
|
|
763
1102
|
});
|
|
764
1103
|
}
|
|
765
1104
|
/**
|