firefly-compiler 0.4.36 → 0.4.46
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/.hintrc +4 -4
- package/.vscode/settings.json +4 -4
- package/bin/Release.ff +99 -49
- package/bin/firefly.mjs +1 -1
- package/compiler/Builder.ff +257 -257
- package/compiler/Compiler.ff +227 -227
- package/compiler/Dependencies.ff +186 -186
- package/compiler/DependencyLock.ff +17 -17
- package/compiler/JsEmitter.ff +946 -946
- package/compiler/LspHook.ff +202 -202
- package/compiler/ModuleCache.ff +178 -178
- package/compiler/Workspace.ff +88 -88
- package/core/.firefly/include/package-lock.json +394 -394
- package/core/.firefly/include/package.json +5 -5
- package/core/.firefly/include/prepare.sh +1 -1
- package/core/.firefly/package.ff +2 -2
- package/core/Array.ff +265 -265
- package/core/Atomic.ff +64 -64
- package/core/Box.ff +7 -7
- package/core/BrowserSystem.ff +40 -37
- package/core/Buffer.ff +3 -3
- package/core/BuildSystem.ff +148 -145
- package/core/Crypto.ff +96 -95
- package/core/Equal.ff +36 -36
- package/core/HttpClient.ff +87 -87
- package/core/Instant.ff +17 -0
- package/core/JsSystem.ff +69 -69
- package/core/Json.ff +434 -434
- package/core/List.ff +415 -415
- package/core/Lock.ff +144 -144
- package/core/NodeSystem.ff +189 -189
- package/core/Ordering.ff +161 -161
- package/core/Path.ff +401 -401
- package/core/Random.ff +134 -134
- package/core/RbMap.ff +216 -216
- package/core/Show.ff +43 -43
- package/core/SourceLocation.ff +68 -68
- package/core/Stream.ff +1 -1
- package/core/Task.ff +141 -141
- package/experimental/benchmarks/ListGrab.ff +23 -23
- package/experimental/benchmarks/ListGrab.java +55 -55
- package/experimental/benchmarks/Pyrotek45.ff +30 -30
- package/experimental/benchmarks/Pyrotek45.java +64 -64
- package/experimental/bidirectional/Bidi.ff +88 -88
- package/experimental/random/Index.ff +53 -53
- package/experimental/random/Process.ff +120 -120
- package/experimental/random/RunLength.ff +3 -3
- package/experimental/random/Scrape.ff +51 -51
- package/experimental/random/Symbols.ff +73 -73
- package/experimental/random/Tensor.ff +52 -52
- package/experimental/random/Units.ff +36 -36
- package/experimental/s3/S3TestAuthorizationHeader.ff +38 -38
- package/experimental/s3/S3TestPut.ff +15 -15
- package/experimental/tests/TestJson.ff +26 -26
- package/firefly.sh +0 -0
- package/fireflysite/Main.ff +13 -13
- package/lsp/.firefly/package.ff +1 -1
- package/lsp/CompletionHandler.ff +811 -811
- package/lsp/Handler.ff +714 -714
- package/lsp/HoverHandler.ff +79 -79
- package/lsp/LanguageServer.ff +272 -272
- package/lsp/SignatureHelpHandler.ff +55 -55
- package/lsp/SymbolHandler.ff +181 -181
- package/lsp/TestReferences.ff +16 -16
- package/lsp/TestReferencesCase.ff +7 -7
- package/lsp/stderr.txt +1 -1
- package/lsp/stdout.txt +34 -34
- package/lux/.firefly/package.ff +1 -1
- package/lux/Css.ff +648 -648
- package/lux/CssTest.ff +48 -48
- package/lux/Lux.ff +487 -487
- package/lux/LuxEvent.ff +116 -116
- package/lux/Main.ff +128 -128
- package/lux/Main2.ff +144 -144
- package/output/js/ff/compiler/Builder.mjs +43 -43
- package/output/js/ff/compiler/Dependencies.mjs +3 -3
- package/output/js/ff/core/Array.mjs +59 -59
- package/output/js/ff/core/Atomic.mjs +36 -36
- package/output/js/ff/core/BrowserSystem.mjs +19 -11
- package/output/js/ff/core/Buffer.mjs +7 -7
- package/output/js/ff/core/BuildSystem.mjs +38 -30
- package/output/js/ff/core/Crypto.mjs +67 -68
- package/output/js/ff/core/HttpClient.mjs +24 -24
- package/output/js/ff/core/Instant.mjs +38 -0
- package/output/js/ff/core/Json.mjs +147 -147
- package/output/js/ff/core/List.mjs +50 -50
- package/output/js/ff/core/Lock.mjs +97 -97
- package/output/js/ff/core/NodeSystem.mjs +77 -77
- package/output/js/ff/core/Ordering.mjs +8 -8
- package/output/js/ff/core/Path.mjs +231 -231
- package/output/js/ff/core/Random.mjs +56 -56
- package/output/js/ff/core/Stream.mjs +2 -2
- package/output/js/ff/core/Task.mjs +31 -31
- package/package.json +29 -29
- package/rpc/.firefly/package.ff +1 -1
- package/rpc/Rpc.ff +69 -69
- package/s3/.firefly/package.ff +1 -0
- package/{experimental/s3 → s3}/S3.ff +92 -92
- package/unsafejs/UnsafeJs.ff +19 -19
- package/vscode/LICENSE.txt +21 -21
- package/vscode/Prepublish.ff +15 -15
- package/vscode/README.md +16 -16
- package/vscode/client/package.json +22 -22
- package/vscode/client/src/extension.ts +104 -104
- package/vscode/icons/firefly-icon.svg +10 -10
- package/vscode/language-configuration.json +61 -61
- package/vscode/package-lock.json +3623 -3623
- package/vscode/package.json +160 -160
- package/vscode/snippets.json +241 -241
- package/webserver/.firefly/include/package-lock.json +16 -16
- package/webserver/.firefly/include/package.json +5 -5
- package/webserver/.firefly/package.ff +2 -2
- package/webserver/WebServer.ff +685 -685
- package/websocket/.firefly/package.ff +1 -1
- package/websocket/WebSocket.ff +131 -131
- package/crypto/SubtleCrypto.ff +0 -149
package/webserver/WebServer.ff
CHANGED
|
@@ -1,685 +1,685 @@
|
|
|
1
|
-
capability WebServer(
|
|
2
|
-
intenalSystem: NodeSystem
|
|
3
|
-
internalHost: String
|
|
4
|
-
internalPort: Int
|
|
5
|
-
mutable internalJsWebSockets: JsValue
|
|
6
|
-
mutable internalJsWildcard: JsValue
|
|
7
|
-
mutable internalJsHosts: Array[JsValue]
|
|
8
|
-
mutable internalJsApp: JsValue
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
class WebRequest[T](
|
|
12
|
-
internalJsApp: JsValue
|
|
13
|
-
internalJsContext: JsValue
|
|
14
|
-
internalMethod: String
|
|
15
|
-
internalPath: String
|
|
16
|
-
internalQuery: String
|
|
17
|
-
internalHeaders: StringMap[String]
|
|
18
|
-
internalJsResponse: JsValue
|
|
19
|
-
internalResponse: T
|
|
20
|
-
internalResponseHeaders: StringMap[String]
|
|
21
|
-
internalResponseChunks: Array[JsValue]
|
|
22
|
-
mutable internalResponseStatus: String
|
|
23
|
-
mutable internalCloseConnection: Bool
|
|
24
|
-
mutable internalResolveFlush: JsValue
|
|
25
|
-
mutable internalContentSize: Int
|
|
26
|
-
mutable internalResponded: Bool
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
capability WebResponse {}
|
|
30
|
-
|
|
31
|
-
data MultipartField {}
|
|
32
|
-
|
|
33
|
-
capability WebSocketConnection {}
|
|
34
|
-
|
|
35
|
-
new(
|
|
36
|
-
system: NodeSystem
|
|
37
|
-
host: String
|
|
38
|
-
port: Int
|
|
39
|
-
): WebServer {
|
|
40
|
-
WebServer(system, host, port, system.js().null(), system.js().null(), [].toArray(), system.js().null())
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
extend self: WebServer {
|
|
44
|
-
|
|
45
|
-
enableWebSockets(
|
|
46
|
-
maxPayloadBytes: Int = 16384
|
|
47
|
-
maxBackpressureBytes: Int = 65536
|
|
48
|
-
closeDueToMaxBackpressureBytes: Bool = False
|
|
49
|
-
maxLifetime: Option[Duration] = None
|
|
50
|
-
idleTimeout: Option[Duration] = None
|
|
51
|
-
) {
|
|
52
|
-
self.internalJsWebSockets = self.intenalSystem.js().object()
|
|
53
|
-
self.internalJsWebSockets.set("maxPayloadLength", maxPayloadBytes)
|
|
54
|
-
self.internalJsWebSockets.set("maxBackpressureBytes", maxBackpressureBytes)
|
|
55
|
-
self.internalJsWebSockets.set("closeOnBackpressureLimit", closeDueToMaxBackpressureBytes)
|
|
56
|
-
maxLifetime.each {self.internalJsWebSockets.set("maxLifetime", (_.seconds / 60).round().toInt())}
|
|
57
|
-
idleTimeout.each {self.internalJsWebSockets.set("idleTimeout", _.seconds.round().toInt())}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
addHost(
|
|
61
|
-
hostname: String
|
|
62
|
-
key: Option[Path] = None
|
|
63
|
-
certificate: Option[Path] = None
|
|
64
|
-
certificateAuthority: Option[Path] = None
|
|
65
|
-
passphrase: Option[String] = None
|
|
66
|
-
dhParameters: Option[Path] = None
|
|
67
|
-
ciphers: Option[String] = None
|
|
68
|
-
preferLowMemoryUsage: Bool = False
|
|
69
|
-
) {
|
|
70
|
-
let object = self.intenalSystem.js().object().with("hostname", hostname)
|
|
71
|
-
key.each {object.set("key_file_name", _.absolute())}
|
|
72
|
-
certificate.each {object.set("cert_file_name", _.absolute())}
|
|
73
|
-
certificateAuthority.each {object.set("ca_file_name", _.absolute())}
|
|
74
|
-
passphrase.each {object.set("passphrase", _)}
|
|
75
|
-
dhParameters.each {object.set("dh_params_file_name", _.absolute())}
|
|
76
|
-
ciphers.each {object.set("ssl_ciphers", _)}
|
|
77
|
-
if(preferLowMemoryUsage) {object.set("ssl_prefer_low_memory_usage", True)}
|
|
78
|
-
self.internalJsHosts.push(object)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
setWildcard(
|
|
82
|
-
key: Option[Path] = None
|
|
83
|
-
certificate: Option[Path] = None
|
|
84
|
-
certificateAuthority: Option[Path] = None
|
|
85
|
-
passphrase: Option[String] = None
|
|
86
|
-
dhParameters: Option[Path] = None
|
|
87
|
-
ciphers: Option[String] = None
|
|
88
|
-
preferLowMemoryUsage: Bool = False
|
|
89
|
-
) {
|
|
90
|
-
let object = self.intenalSystem.js().object()
|
|
91
|
-
key.each {object.set("key_file_name", _.absolute())}
|
|
92
|
-
certificate.each {object.set("cert_file_name", _.absolute())}
|
|
93
|
-
certificateAuthority.each {object.set("ca_file_name", _.absolute())}
|
|
94
|
-
passphrase.each {object.set("passphrase", _)}
|
|
95
|
-
dhParameters.each {object.set("dh_params_file_name", _.absolute())}
|
|
96
|
-
ciphers.each {object.set("ssl_ciphers", _)}
|
|
97
|
-
if(preferLowMemoryUsage) {object.set("ssl_prefer_low_memory_usage", True)}
|
|
98
|
-
self.internalJsWildcard = object
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
listen(handler: WebRequest[WebResponse] => Unit)
|
|
102
|
-
target node async """
|
|
103
|
-
import * as uws from 'uWebSockets.js'
|
|
104
|
-
ff_core_Task.Task_throwIfAborted($task);
|
|
105
|
-
await new Promise((resolveServer, rejectServer) => {
|
|
106
|
-
self_.internalJsApp_ = self_.internalJsWilcard_ ? uws.SSLApp(self_.internalJsWilcard_)
|
|
107
|
-
: self_.internalJsHosts_.array.some(h => h.key_file_name) ? uws.SSLApp() : uws.App();
|
|
108
|
-
self_.internalJsHosts_.array.forEach(h => self_.internalJsApp_.addServerName(h.hostname, h));
|
|
109
|
-
const handleRequest = (res, req, context) => {
|
|
110
|
-
let abortedByUws = false;
|
|
111
|
-
const method = req.getMethod();
|
|
112
|
-
const path = req.getUrl();
|
|
113
|
-
const query = req.getQuery() || '';
|
|
114
|
-
const headers = new Map();
|
|
115
|
-
req.forEach((key, value) => headers.set(key, value));
|
|
116
|
-
const chunks = internalArray_();
|
|
117
|
-
const task = ff_core_Task.Task_spawn($task, async task => {
|
|
118
|
-
try {
|
|
119
|
-
ff_core_Task.Task_throwIfAborted(task);
|
|
120
|
-
const webRequest = WebRequest(
|
|
121
|
-
self_.internalJsApp_, context,
|
|
122
|
-
method, path, query, headers,
|
|
123
|
-
res, null,
|
|
124
|
-
new Map(), internalArray_(),
|
|
125
|
-
"200 OK", false, null, -1, false
|
|
126
|
-
);
|
|
127
|
-
await handler_(webRequest, task);
|
|
128
|
-
ff_core_Task.Task_throwIfAborted(task);
|
|
129
|
-
if(context == null && !webRequest.internalResponded_ && webRequest.internalContentSize_ >= 0) {
|
|
130
|
-
await WebRequest_flush$(webRequest, task);
|
|
131
|
-
ff_core_Task.Task_throwIfAborted(task);
|
|
132
|
-
}
|
|
133
|
-
if(context == null && !webRequest.internalResponded_) await new Promise((resolve, reject) => {
|
|
134
|
-
res.cork(() => {
|
|
135
|
-
try {
|
|
136
|
-
ff_core_Task.Task_throwIfAborted(task);
|
|
137
|
-
if(!webRequest.internalResolveFlush_) {
|
|
138
|
-
res.writeStatus(webRequest.internalResponseStatus_);
|
|
139
|
-
webRequest.internalResponseHeaders_.forEach((value, key) => res.writeHeader(key, value));
|
|
140
|
-
}
|
|
141
|
-
const chunk = internalChunksToChunk_(webRequest.internalResponseChunks_);
|
|
142
|
-
res.end(chunk, webRequest.internalCloseConnection_);
|
|
143
|
-
resolve();
|
|
144
|
-
} catch(e) {
|
|
145
|
-
reject(e);
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
} catch(e) {
|
|
150
|
-
if(!abortedByUws) console.error(e);
|
|
151
|
-
if(!abortedByUws && context == null) res.cork(() => {
|
|
152
|
-
if(!abortedByUws) res.writeStatus("500 Internal Server Error");
|
|
153
|
-
if(!abortedByUws) res.end();
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
res.onAborted(() => {
|
|
158
|
-
abortedByUws = true;
|
|
159
|
-
ff_core_Task.Task_abort(task);
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
self_.internalJsApp_.any('/*', handleRequest);
|
|
163
|
-
if(self_.internalJsWebSockets_) self_.internalJsApp_.ws("/*", {
|
|
164
|
-
...self_.internalJsWebSockets_,
|
|
165
|
-
upgrade: handleRequest,
|
|
166
|
-
open: ws => ws.getUserData().onOpen(ws),
|
|
167
|
-
message: (ws, m, b) => ws.getUserData().onMessage(m, b),
|
|
168
|
-
close: ws => ws.getUserData().onClose(),
|
|
169
|
-
});
|
|
170
|
-
self_.internalJsApp_.listen(self_.internalHost_, self_.internalPort_, listenSocket => {
|
|
171
|
-
if(listenSocket) {
|
|
172
|
-
const abort = () => {
|
|
173
|
-
$task.controller.signal.removeEventListener('abort', abort);
|
|
174
|
-
uws.us_listen_socket_close(listenSocket);
|
|
175
|
-
self_.internalJsApp_.close();
|
|
176
|
-
}
|
|
177
|
-
$task.controller.signal.addEventListener('abort', abort);
|
|
178
|
-
} else {
|
|
179
|
-
rejectServer();
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
"""
|
|
184
|
-
|
|
185
|
-
publishBuffer(topic: String, message: Buffer) // Should this be corked?
|
|
186
|
-
target js async """
|
|
187
|
-
if(self_.internalJsApp_) self_.internalJsApp_.publish(topic_, message_.buffer, true);
|
|
188
|
-
"""
|
|
189
|
-
|
|
190
|
-
publishText(topic: String, message: String) // Should this be corked?
|
|
191
|
-
target js async """
|
|
192
|
-
if(self_.internalJsApp_) self_.internalJsApp_.publish(topic_, message_, false);
|
|
193
|
-
"""
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
extend self[T]: WebRequest[T] {
|
|
198
|
-
|
|
199
|
-
readMethod(): String {
|
|
200
|
-
self.internalMethod
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
readPath(): String {
|
|
204
|
-
self.internalPath
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
readRawQueryString(): String {
|
|
208
|
-
self.internalQuery
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
readQuery(name: String): Option[String]
|
|
212
|
-
target node sync """
|
|
213
|
-
if(self_.internalQuery_.length === 0) return ff_core_Option.None();
|
|
214
|
-
const ps = self_.internalQuery_.split('&');
|
|
215
|
-
const n = name_ + '=';
|
|
216
|
-
const r = ps.find(p => p === name_ || p.startsWith(n));
|
|
217
|
-
if(r == null) return ff_core_Option.None();
|
|
218
|
-
return ff_core_Option.Some(decodeURIComponent(r.slice(name_.length + 1)));
|
|
219
|
-
"""
|
|
220
|
-
|
|
221
|
-
readHeader(lowerCaseName: String): Option[String] {
|
|
222
|
-
self.internalHeaders.get(lowerCaseName)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
eachHeader(body: (String, String) => Unit) {
|
|
226
|
-
self.internalHeaders.each {body(_, _)}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
extend self[T]: WebRequest[T] {
|
|
232
|
-
|
|
233
|
-
writeStatus(codeAndMessage: String) {
|
|
234
|
-
self.internalResponseStatus = codeAndMessage
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
writeHeader(name: String, value: String) {
|
|
238
|
-
self.internalResponseHeaders.set(name, value)
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
writeContentSize(contentSize: Int) {
|
|
242
|
-
if(contentSize >= 0) {
|
|
243
|
-
self.internalContentSize = contentSize
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
writeText(data: String) {
|
|
248
|
-
self.internalResponseChunks.push(internalTextChunk(data))
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
writeLine(data: String) {
|
|
252
|
-
self.writeText(data + "\n")
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
writeBuffer(data: Buffer) {
|
|
256
|
-
self.internalResponseChunks.push(internalBufferChunk(data))
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
writeStream(data: Stream[Buffer]) {
|
|
260
|
-
data.each {self.writeBuffer(_)}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
extend self[T]: WebRequest[T] {
|
|
266
|
-
|
|
267
|
-
publishBuffer(topic: String, message: Buffer) // Should this be corked?
|
|
268
|
-
target js sync """
|
|
269
|
-
self_.internalJsApp_.publish(topic_, message_.buffer, true);
|
|
270
|
-
"""
|
|
271
|
-
|
|
272
|
-
publishText(topic: String, message: String) // Should this be corked?
|
|
273
|
-
target js sync """
|
|
274
|
-
self_.internalJsApp_.publish(topic_, message_, false);
|
|
275
|
-
"""
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
extend self[T]: WebRequest[T] {
|
|
280
|
-
|
|
281
|
-
closeConnection() {
|
|
282
|
-
self.internalCloseConnection = True
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
readRemoteAddress(): String
|
|
286
|
-
target js sync """
|
|
287
|
-
return new TextDecoder().decode(self_.internalJsResponse_.getRemoteAddressAsText())
|
|
288
|
-
"""
|
|
289
|
-
|
|
290
|
-
readProxiedRemoteAddress(): String
|
|
291
|
-
target js sync """
|
|
292
|
-
return new TextDecoder().decode(self_.internalJsResponse_.getProxiedRemoteAddressAsText())
|
|
293
|
-
"""
|
|
294
|
-
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
extend self: WebRequest[WebResponse] {
|
|
298
|
-
|
|
299
|
-
flush()
|
|
300
|
-
target js async """
|
|
301
|
-
if(!self_.internalResolveFlush_) {
|
|
302
|
-
self_.internalResolveFlush_ = () => {};
|
|
303
|
-
self_.internalJsResponse_.cork(() => {
|
|
304
|
-
self_.internalJsResponse_.writeStatus(self_.internalResponseStatus_);
|
|
305
|
-
self_.internalResponseHeaders_.forEach((value, key) =>
|
|
306
|
-
self_.internalJsResponse_.writeHeader(key, value)
|
|
307
|
-
);
|
|
308
|
-
self_.internalJsResponse_.onWritable(() => {
|
|
309
|
-
self_.internalResolveFlush_();
|
|
310
|
-
return true;
|
|
311
|
-
});
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
while(self_.internalResponseChunks_.array.length !== 0) {
|
|
315
|
-
await new Promise((resolve, reject) => {
|
|
316
|
-
self_.internalJsResponse_.cork(() => {
|
|
317
|
-
let backpressure = false;
|
|
318
|
-
let i = 0;
|
|
319
|
-
for(i = 0; i < self_.internalResponseChunks_.array.length && !backpressure; i++) {
|
|
320
|
-
let chunk = self_.internalResponseChunks_.array[i];
|
|
321
|
-
if(chunk && (chunk.byteLength || chunk.length)) {
|
|
322
|
-
if(typeof chunk !== 'string') {
|
|
323
|
-
chunk = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
324
|
-
} else if(self_.internalContentSize_ >= 0) {
|
|
325
|
-
chunk = new TextEncoder().encode(chunk);
|
|
326
|
-
}
|
|
327
|
-
if(self_.internalContentSize_ >= 0) {
|
|
328
|
-
const lastOffset = self_.internalJsResponse_.getWriteOffset();
|
|
329
|
-
const [ok, responded] = self_.internalJsResponse_.tryEnd(chunk, self_.internalContentSize_);
|
|
330
|
-
if(responded) self_.internalResponded_ = true;
|
|
331
|
-
if(!ok) {
|
|
332
|
-
const written = self_.internalJsResponse_.getWriteOffset() - lastOffset;
|
|
333
|
-
backpressure = true;
|
|
334
|
-
self_.internalResponseChunks_.array[i] =
|
|
335
|
-
new Uint8Array(chunk.buffer, chunk.byteOffset + written, chunk.byteLength - written);
|
|
336
|
-
i -= 1;
|
|
337
|
-
}
|
|
338
|
-
} else {
|
|
339
|
-
backpressure = !self_.internalJsResponse_.write(chunk);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
if(i > 0) self_.internalResponseChunks_.array.splice(0, i);
|
|
344
|
-
if(!backpressure) {
|
|
345
|
-
resolve();
|
|
346
|
-
} else {
|
|
347
|
-
self_.internalResolveFlush_ = resolve;
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
"""
|
|
353
|
-
|
|
354
|
-
flushText(data: String) {
|
|
355
|
-
self.writeText(data)
|
|
356
|
-
self.flush()
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
flushBuffer(data: Buffer) {
|
|
360
|
-
self.writeBuffer(data)
|
|
361
|
-
self.flush()
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
flushStream(data: Stream[Buffer]) {
|
|
365
|
-
data.each {self.flushBuffer(_)}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
extend self: WebRequest[WebResponse] {
|
|
371
|
-
|
|
372
|
-
readText(encoding: String = "utf8"): String {
|
|
373
|
-
self.readStream(1073741823).toString(encoding)
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
readBuffer(): Buffer {
|
|
377
|
-
self.readStream(1073741823).toBuffer()
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
readStream(maxPendingBytes: Int = 65536): Stream[Buffer]
|
|
381
|
-
target js async """
|
|
382
|
-
const front = [];
|
|
383
|
-
const back = [];
|
|
384
|
-
let pendingBytes = 0
|
|
385
|
-
let closed = false;
|
|
386
|
-
let callResolve = null;
|
|
387
|
-
let callReject = null;
|
|
388
|
-
self_.internalJsResponse_.onData((chunk, isLast) => {
|
|
389
|
-
if(closed) return;
|
|
390
|
-
if(isLast) closed = true;
|
|
391
|
-
else chunk = cunk.slice();
|
|
392
|
-
pendingBytes += chunk.byteLength;
|
|
393
|
-
back.push(new DataView(chunk));
|
|
394
|
-
if(pendingBytes >= maxPendingBytes_) self_.internalJsResponse_.pause();
|
|
395
|
-
if(callResolve) callResolve();
|
|
396
|
-
});
|
|
397
|
-
const abort = () => {
|
|
398
|
-
$task.controller.signal.removeEventListener('abort', abort);
|
|
399
|
-
if(callReject) callReject();
|
|
400
|
-
}
|
|
401
|
-
$task.controller.signal.addEventListener('abort', abort);
|
|
402
|
-
return ff_core_Stream.Stream(
|
|
403
|
-
async () => {
|
|
404
|
-
while(true) {
|
|
405
|
-
ff_core_Task.Task_throwIfAborted($task);
|
|
406
|
-
if(front.length === 0) {
|
|
407
|
-
while(back.length !== 0) front.push(back.pop());
|
|
408
|
-
}
|
|
409
|
-
if(front.length !== 0) {
|
|
410
|
-
const chunk = front.pop();
|
|
411
|
-
const paused = pendingBytes >= maxPendingBytes_;
|
|
412
|
-
pendingBytes -= chunk.byteLength;
|
|
413
|
-
if(!closed && paused && pendingBytes < maxPendingBytes_) self_.internalJsResponse_.unpause();
|
|
414
|
-
return ff_core_Option.Some(chunk);
|
|
415
|
-
} else if(closed) {
|
|
416
|
-
return ff_core_Option.None();
|
|
417
|
-
} else {
|
|
418
|
-
await new Promise((resolve, reject) => {
|
|
419
|
-
callResolve = resolve;
|
|
420
|
-
callReject = reject;
|
|
421
|
-
});
|
|
422
|
-
callResolve = null;
|
|
423
|
-
callReject = null;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
},
|
|
427
|
-
async () => {
|
|
428
|
-
closed = true;
|
|
429
|
-
ff_core_Task.Task_throwIfAborted($task);
|
|
430
|
-
if(callResolve) callResolve();
|
|
431
|
-
}
|
|
432
|
-
)
|
|
433
|
-
"""
|
|
434
|
-
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
extend self[T]: WebRequest[T] {
|
|
438
|
-
|
|
439
|
-
needsWebSocket(): Bool
|
|
440
|
-
target js sync """
|
|
441
|
-
return self_.internalJsContext_ != null;
|
|
442
|
-
"""
|
|
443
|
-
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
extend self: WebRequest[WebResponse] {
|
|
447
|
-
|
|
448
|
-
openWebSocket(): WebSocketConnection
|
|
449
|
-
target js async """
|
|
450
|
-
ff_core_Task.Task_throwIfAborted($task);
|
|
451
|
-
const userData = {};
|
|
452
|
-
const wrapper = {front: [], back: [], listeners: [], task: $task};
|
|
453
|
-
const promise = new Promise((resolve, reject) => {
|
|
454
|
-
const abort = () => {wrapper.webSocket = null; reject()};
|
|
455
|
-
userData.onOpen = ws => {wrapper.webSocket = ws; resolve(wrapper)};
|
|
456
|
-
userData.onClose = () => {
|
|
457
|
-
$task.controller.signal.removeEventListener('abort', abort);
|
|
458
|
-
abort();
|
|
459
|
-
for(let i = 0; i < wrapper.listeners.length; i++) wrapper.listeners[i]();
|
|
460
|
-
wrapper.listeners.length = 0;
|
|
461
|
-
};
|
|
462
|
-
userData.onMessage = (message, isBinary) => {
|
|
463
|
-
wrapper.back.push(isBinary ? new DataView(message) : new TextDecoder().decode(message));
|
|
464
|
-
for(let i = 0; i < wrapper.listeners.length; i++) wrapper.listeners[i]();
|
|
465
|
-
wrapper.listeners.length = 0;
|
|
466
|
-
};
|
|
467
|
-
$task.controller.signal.addEventListener('abort', abort);
|
|
468
|
-
});
|
|
469
|
-
self_.internalJsResponse_.cork(() => {
|
|
470
|
-
ff_core_Task.Task_throwIfAborted($task);
|
|
471
|
-
self_.internalJsResponse_.upgrade(
|
|
472
|
-
userData,
|
|
473
|
-
self_.internalHeaders_.get('sec-websocket-key'),
|
|
474
|
-
self_.internalHeaders_.get('sec-websocket-protocol'),
|
|
475
|
-
self_.internalHeaders_.get('sec-websocket-extensions'),
|
|
476
|
-
self_.internalJsContext_
|
|
477
|
-
)
|
|
478
|
-
});
|
|
479
|
-
return await promise;
|
|
480
|
-
"""
|
|
481
|
-
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
extend self: WebSocketConnection {
|
|
485
|
-
|
|
486
|
-
readText(encoding: String = "utf8"): Option[String] {
|
|
487
|
-
self.readAny {_} {_.toString(encoding)}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
readBuffer(): Option[Buffer] {
|
|
491
|
-
self.readAny {_.toBuffer()} {_}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
readAny[T](fromText: String => T, fromBuffer: Buffer => T): Option[T]
|
|
495
|
-
target node async """
|
|
496
|
-
ff_core_Task.Task_throwIfAborted($task);
|
|
497
|
-
while(self_.front.length === 0) {
|
|
498
|
-
while(self_.back.length !== 0) {
|
|
499
|
-
self_.front.push(self_.back.pop());
|
|
500
|
-
}
|
|
501
|
-
if(self_.front.length !== 0) break;
|
|
502
|
-
if(self_.webSocket == null) return ff_core_Option.None();
|
|
503
|
-
let abort = null;
|
|
504
|
-
try {
|
|
505
|
-
await new Promise((resolve, reject) => {
|
|
506
|
-
self_.listeners.push(resolve);
|
|
507
|
-
if($task !== self_.task) {
|
|
508
|
-
abort = () => {
|
|
509
|
-
self_.listeners = self_.listeners.filter(l => l != resolve);
|
|
510
|
-
reject();
|
|
511
|
-
};
|
|
512
|
-
$task.controller.signal.addEventListener('abort', abort);
|
|
513
|
-
}
|
|
514
|
-
});
|
|
515
|
-
} finally {
|
|
516
|
-
if($task !== self_.task) {
|
|
517
|
-
$task.controller.signal.removeEventListener('abort', abort);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
const data = self_.front.pop();
|
|
522
|
-
if(typeof data === 'string') return ff_core_Option.Some(await fromText_(data));
|
|
523
|
-
return ff_core_Option.Some(await fromBuffer_(data));
|
|
524
|
-
"""
|
|
525
|
-
|
|
526
|
-
readStream(): Stream[Buffer] {
|
|
527
|
-
Stream {self.readBuffer()} {self.close()}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
writeBuffer(message: Buffer)
|
|
531
|
-
target node async """
|
|
532
|
-
await internalSend_$(self_, () => self_.webSocket.send(message_.buffer, true) !== 2, $task);
|
|
533
|
-
"""
|
|
534
|
-
|
|
535
|
-
writeText(message: String)
|
|
536
|
-
target node async """
|
|
537
|
-
await internalSend_$(self_, () => self_.webSocket.send(message_, false) !== 2, $task);
|
|
538
|
-
"""
|
|
539
|
-
|
|
540
|
-
publishBuffer(topic: String, message: Buffer)
|
|
541
|
-
target node async """
|
|
542
|
-
await internalSend_$(self_, () => self_.webSocket.publish(topic_, message_.buffer, true), $task);
|
|
543
|
-
"""
|
|
544
|
-
|
|
545
|
-
publishText(topic: String, message: String)
|
|
546
|
-
target node async """
|
|
547
|
-
await internalSend_$(self_, () => self_.webSocket.publish(topic_, message_, false), $task);
|
|
548
|
-
"""
|
|
549
|
-
|
|
550
|
-
subscribe(topic: String)
|
|
551
|
-
target node async """
|
|
552
|
-
if(self_.webSocket == null) throw new Error("WebSocket is closed");
|
|
553
|
-
self_.webSocket.subscribe(topic_);
|
|
554
|
-
"""
|
|
555
|
-
|
|
556
|
-
unsubscribe(topic: String)
|
|
557
|
-
target node async """
|
|
558
|
-
if(self_.webSocket == null) throw new Error("WebSocket is closed");
|
|
559
|
-
self_.webSocket.unsubscribe(topic_);
|
|
560
|
-
"""
|
|
561
|
-
|
|
562
|
-
isSubscribedTo(topic: String): Bool
|
|
563
|
-
target node async """
|
|
564
|
-
if(self_.webSocket == null) throw new Error("WebSocket is closed");
|
|
565
|
-
return self_.webSocket.isSubscribed(topic_);
|
|
566
|
-
"""
|
|
567
|
-
|
|
568
|
-
subscriptions(): Array[String]
|
|
569
|
-
target node async """
|
|
570
|
-
if(self_.webSocket == null) throw new Error("WebSocket is closed");
|
|
571
|
-
return self_.webSocket.getTopics();
|
|
572
|
-
"""
|
|
573
|
-
|
|
574
|
-
close(): Unit
|
|
575
|
-
target node async """
|
|
576
|
-
if(self_.webSocket != null) {
|
|
577
|
-
self_.webSocket.close();
|
|
578
|
-
self_.webSocket = null;
|
|
579
|
-
}
|
|
580
|
-
"""
|
|
581
|
-
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
internalSend[T](self: WebSocketConnection, send: () => Bool): JsValue
|
|
585
|
-
target node async """
|
|
586
|
-
let abort = null;
|
|
587
|
-
try {
|
|
588
|
-
await new Promise((resolve, reject) => {
|
|
589
|
-
if(self_.webSocket == null) new Error("WebSocket is closed");
|
|
590
|
-
else self_.webSocket.cork(() => {
|
|
591
|
-
if($task !== self_.task) {
|
|
592
|
-
abort = () => {
|
|
593
|
-
self_.listeners = self_.listeners.filter(l => l != resolve);
|
|
594
|
-
reject();
|
|
595
|
-
};
|
|
596
|
-
$task.controller.signal.addEventListener('abort', abort);
|
|
597
|
-
}
|
|
598
|
-
if(self_.webSocket == null) {
|
|
599
|
-
reject(new Error("WebSocket is closed"));
|
|
600
|
-
} else if(!send_()) {
|
|
601
|
-
reject(new Error("Dropped message due to backpressure"));
|
|
602
|
-
} else {
|
|
603
|
-
resolve();
|
|
604
|
-
}
|
|
605
|
-
})
|
|
606
|
-
});
|
|
607
|
-
} finally {
|
|
608
|
-
if($task !== self_.task) {
|
|
609
|
-
$task.controller.signal.removeEventListener('abort', abort);
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
"""
|
|
613
|
-
|
|
614
|
-
internalArray[T](): Array[T] {
|
|
615
|
-
Array.new()
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
internalTextChunk(data: String): JsValue
|
|
619
|
-
target js sync """
|
|
620
|
-
return data_;
|
|
621
|
-
"""
|
|
622
|
-
|
|
623
|
-
internalBufferChunk(data: Buffer): JsValue
|
|
624
|
-
target js sync """
|
|
625
|
-
return data_;
|
|
626
|
-
"""
|
|
627
|
-
|
|
628
|
-
internalChunksToChunk(chunks: Array[JsValue]): JsValue
|
|
629
|
-
target js sync """
|
|
630
|
-
const chunks = chunks_.array;
|
|
631
|
-
if(chunks.length === 0) return;
|
|
632
|
-
const firstIsString = typeof chunks[0] === 'string';
|
|
633
|
-
if(firstIsString && chunks.length === 1) return chunks[0];
|
|
634
|
-
if(firstIsString && chunks.every(c => typeof c === 'string')) {
|
|
635
|
-
let result = chunks[0];
|
|
636
|
-
for(let i = 1; i < chunks.length; i++) {
|
|
637
|
-
result += chunks[i];
|
|
638
|
-
}
|
|
639
|
-
return result;
|
|
640
|
-
}
|
|
641
|
-
let totalLength = 0;
|
|
642
|
-
for(let i = 0; i < chunks.length; i++) {
|
|
643
|
-
if(typeof chunks[i] === 'string') chunks[i] = new TextEncoder().encode(chunks[i]);
|
|
644
|
-
totalLength += chunks[i].byteLength;
|
|
645
|
-
}
|
|
646
|
-
const result = new Uint8Array(totalLength);
|
|
647
|
-
let offset = 0;
|
|
648
|
-
for(let i = 0; i < chunks.length; i++) {
|
|
649
|
-
const chunk = chunks[i];
|
|
650
|
-
const uint8Array = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
651
|
-
result.set(uint8Array, offset);
|
|
652
|
-
offset += chunk.byteLength;
|
|
653
|
-
}
|
|
654
|
-
return result;
|
|
655
|
-
"""
|
|
656
|
-
|
|
657
|
-
parseMultipartFields(data: Buffer, contentType: String): Option[Array[MultipartField]]
|
|
658
|
-
target js sync """
|
|
659
|
-
const parts = getParts(data_, contentType_);
|
|
660
|
-
return parts == null ? ff_core_Option.None() : ff_core_Option.Some(parts);
|
|
661
|
-
"""
|
|
662
|
-
|
|
663
|
-
extend self: MultipartField {
|
|
664
|
-
|
|
665
|
-
data(): Buffer
|
|
666
|
-
target js sync """
|
|
667
|
-
return new DataView(self_.data);
|
|
668
|
-
"""
|
|
669
|
-
|
|
670
|
-
name(): String
|
|
671
|
-
target js sync """
|
|
672
|
-
return self_.name;
|
|
673
|
-
"""
|
|
674
|
-
|
|
675
|
-
type(): Option[String]
|
|
676
|
-
target js sync """
|
|
677
|
-
return self_.type == null ? ff_core_Option.None() : ff_core_Option.Some(self_.type);
|
|
678
|
-
"""
|
|
679
|
-
|
|
680
|
-
filename(): Option[String]
|
|
681
|
-
target js sync """
|
|
682
|
-
return self_.filename == null ? ff_core_Option.None() : ff_core_Option.Some(self_.filename);
|
|
683
|
-
"""
|
|
684
|
-
|
|
685
|
-
}
|
|
1
|
+
capability WebServer(
|
|
2
|
+
intenalSystem: NodeSystem
|
|
3
|
+
internalHost: String
|
|
4
|
+
internalPort: Int
|
|
5
|
+
mutable internalJsWebSockets: JsValue
|
|
6
|
+
mutable internalJsWildcard: JsValue
|
|
7
|
+
mutable internalJsHosts: Array[JsValue]
|
|
8
|
+
mutable internalJsApp: JsValue
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
class WebRequest[T](
|
|
12
|
+
internalJsApp: JsValue
|
|
13
|
+
internalJsContext: JsValue
|
|
14
|
+
internalMethod: String
|
|
15
|
+
internalPath: String
|
|
16
|
+
internalQuery: String
|
|
17
|
+
internalHeaders: StringMap[String]
|
|
18
|
+
internalJsResponse: JsValue
|
|
19
|
+
internalResponse: T
|
|
20
|
+
internalResponseHeaders: StringMap[String]
|
|
21
|
+
internalResponseChunks: Array[JsValue]
|
|
22
|
+
mutable internalResponseStatus: String
|
|
23
|
+
mutable internalCloseConnection: Bool
|
|
24
|
+
mutable internalResolveFlush: JsValue
|
|
25
|
+
mutable internalContentSize: Int
|
|
26
|
+
mutable internalResponded: Bool
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
capability WebResponse {}
|
|
30
|
+
|
|
31
|
+
data MultipartField {}
|
|
32
|
+
|
|
33
|
+
capability WebSocketConnection {}
|
|
34
|
+
|
|
35
|
+
new(
|
|
36
|
+
system: NodeSystem
|
|
37
|
+
host: String
|
|
38
|
+
port: Int
|
|
39
|
+
): WebServer {
|
|
40
|
+
WebServer(system, host, port, system.js().null(), system.js().null(), [].toArray(), system.js().null())
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
extend self: WebServer {
|
|
44
|
+
|
|
45
|
+
enableWebSockets(
|
|
46
|
+
maxPayloadBytes: Int = 16384
|
|
47
|
+
maxBackpressureBytes: Int = 65536
|
|
48
|
+
closeDueToMaxBackpressureBytes: Bool = False
|
|
49
|
+
maxLifetime: Option[Duration] = None
|
|
50
|
+
idleTimeout: Option[Duration] = None
|
|
51
|
+
) {
|
|
52
|
+
self.internalJsWebSockets = self.intenalSystem.js().object()
|
|
53
|
+
self.internalJsWebSockets.set("maxPayloadLength", maxPayloadBytes)
|
|
54
|
+
self.internalJsWebSockets.set("maxBackpressureBytes", maxBackpressureBytes)
|
|
55
|
+
self.internalJsWebSockets.set("closeOnBackpressureLimit", closeDueToMaxBackpressureBytes)
|
|
56
|
+
maxLifetime.each {self.internalJsWebSockets.set("maxLifetime", (_.seconds / 60).round().toInt())}
|
|
57
|
+
idleTimeout.each {self.internalJsWebSockets.set("idleTimeout", _.seconds.round().toInt())}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
addHost(
|
|
61
|
+
hostname: String
|
|
62
|
+
key: Option[Path] = None
|
|
63
|
+
certificate: Option[Path] = None
|
|
64
|
+
certificateAuthority: Option[Path] = None
|
|
65
|
+
passphrase: Option[String] = None
|
|
66
|
+
dhParameters: Option[Path] = None
|
|
67
|
+
ciphers: Option[String] = None
|
|
68
|
+
preferLowMemoryUsage: Bool = False
|
|
69
|
+
) {
|
|
70
|
+
let object = self.intenalSystem.js().object().with("hostname", hostname)
|
|
71
|
+
key.each {object.set("key_file_name", _.absolute())}
|
|
72
|
+
certificate.each {object.set("cert_file_name", _.absolute())}
|
|
73
|
+
certificateAuthority.each {object.set("ca_file_name", _.absolute())}
|
|
74
|
+
passphrase.each {object.set("passphrase", _)}
|
|
75
|
+
dhParameters.each {object.set("dh_params_file_name", _.absolute())}
|
|
76
|
+
ciphers.each {object.set("ssl_ciphers", _)}
|
|
77
|
+
if(preferLowMemoryUsage) {object.set("ssl_prefer_low_memory_usage", True)}
|
|
78
|
+
self.internalJsHosts.push(object)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
setWildcard(
|
|
82
|
+
key: Option[Path] = None
|
|
83
|
+
certificate: Option[Path] = None
|
|
84
|
+
certificateAuthority: Option[Path] = None
|
|
85
|
+
passphrase: Option[String] = None
|
|
86
|
+
dhParameters: Option[Path] = None
|
|
87
|
+
ciphers: Option[String] = None
|
|
88
|
+
preferLowMemoryUsage: Bool = False
|
|
89
|
+
) {
|
|
90
|
+
let object = self.intenalSystem.js().object()
|
|
91
|
+
key.each {object.set("key_file_name", _.absolute())}
|
|
92
|
+
certificate.each {object.set("cert_file_name", _.absolute())}
|
|
93
|
+
certificateAuthority.each {object.set("ca_file_name", _.absolute())}
|
|
94
|
+
passphrase.each {object.set("passphrase", _)}
|
|
95
|
+
dhParameters.each {object.set("dh_params_file_name", _.absolute())}
|
|
96
|
+
ciphers.each {object.set("ssl_ciphers", _)}
|
|
97
|
+
if(preferLowMemoryUsage) {object.set("ssl_prefer_low_memory_usage", True)}
|
|
98
|
+
self.internalJsWildcard = object
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
listen(handler: WebRequest[WebResponse] => Unit)
|
|
102
|
+
target node async """
|
|
103
|
+
import * as uws from 'uWebSockets.js'
|
|
104
|
+
ff_core_Task.Task_throwIfAborted($task);
|
|
105
|
+
await new Promise((resolveServer, rejectServer) => {
|
|
106
|
+
self_.internalJsApp_ = self_.internalJsWilcard_ ? uws.SSLApp(self_.internalJsWilcard_)
|
|
107
|
+
: self_.internalJsHosts_.array.some(h => h.key_file_name) ? uws.SSLApp() : uws.App();
|
|
108
|
+
self_.internalJsHosts_.array.forEach(h => self_.internalJsApp_.addServerName(h.hostname, h));
|
|
109
|
+
const handleRequest = (res, req, context) => {
|
|
110
|
+
let abortedByUws = false;
|
|
111
|
+
const method = req.getMethod();
|
|
112
|
+
const path = req.getUrl();
|
|
113
|
+
const query = req.getQuery() || '';
|
|
114
|
+
const headers = new Map();
|
|
115
|
+
req.forEach((key, value) => headers.set(key, value));
|
|
116
|
+
const chunks = internalArray_();
|
|
117
|
+
const task = ff_core_Task.Task_spawn($task, async task => {
|
|
118
|
+
try {
|
|
119
|
+
ff_core_Task.Task_throwIfAborted(task);
|
|
120
|
+
const webRequest = WebRequest(
|
|
121
|
+
self_.internalJsApp_, context,
|
|
122
|
+
method, path, query, headers,
|
|
123
|
+
res, null,
|
|
124
|
+
new Map(), internalArray_(),
|
|
125
|
+
"200 OK", false, null, -1, false
|
|
126
|
+
);
|
|
127
|
+
await handler_(webRequest, task);
|
|
128
|
+
ff_core_Task.Task_throwIfAborted(task);
|
|
129
|
+
if(context == null && !webRequest.internalResponded_ && webRequest.internalContentSize_ >= 0) {
|
|
130
|
+
await WebRequest_flush$(webRequest, task);
|
|
131
|
+
ff_core_Task.Task_throwIfAborted(task);
|
|
132
|
+
}
|
|
133
|
+
if(context == null && !webRequest.internalResponded_) await new Promise((resolve, reject) => {
|
|
134
|
+
res.cork(() => {
|
|
135
|
+
try {
|
|
136
|
+
ff_core_Task.Task_throwIfAborted(task);
|
|
137
|
+
if(!webRequest.internalResolveFlush_) {
|
|
138
|
+
res.writeStatus(webRequest.internalResponseStatus_);
|
|
139
|
+
webRequest.internalResponseHeaders_.forEach((value, key) => res.writeHeader(key, value));
|
|
140
|
+
}
|
|
141
|
+
const chunk = internalChunksToChunk_(webRequest.internalResponseChunks_);
|
|
142
|
+
res.end(chunk, webRequest.internalCloseConnection_);
|
|
143
|
+
resolve();
|
|
144
|
+
} catch(e) {
|
|
145
|
+
reject(e);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
} catch(e) {
|
|
150
|
+
if(!abortedByUws) console.error(e);
|
|
151
|
+
if(!abortedByUws && context == null) res.cork(() => {
|
|
152
|
+
if(!abortedByUws) res.writeStatus("500 Internal Server Error");
|
|
153
|
+
if(!abortedByUws) res.end();
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
res.onAborted(() => {
|
|
158
|
+
abortedByUws = true;
|
|
159
|
+
ff_core_Task.Task_abort(task);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
self_.internalJsApp_.any('/*', handleRequest);
|
|
163
|
+
if(self_.internalJsWebSockets_) self_.internalJsApp_.ws("/*", {
|
|
164
|
+
...self_.internalJsWebSockets_,
|
|
165
|
+
upgrade: handleRequest,
|
|
166
|
+
open: ws => ws.getUserData().onOpen(ws),
|
|
167
|
+
message: (ws, m, b) => ws.getUserData().onMessage(m, b),
|
|
168
|
+
close: ws => ws.getUserData().onClose(),
|
|
169
|
+
});
|
|
170
|
+
self_.internalJsApp_.listen(self_.internalHost_, self_.internalPort_, listenSocket => {
|
|
171
|
+
if(listenSocket) {
|
|
172
|
+
const abort = () => {
|
|
173
|
+
$task.controller.signal.removeEventListener('abort', abort);
|
|
174
|
+
uws.us_listen_socket_close(listenSocket);
|
|
175
|
+
self_.internalJsApp_.close();
|
|
176
|
+
}
|
|
177
|
+
$task.controller.signal.addEventListener('abort', abort);
|
|
178
|
+
} else {
|
|
179
|
+
rejectServer();
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
publishBuffer(topic: String, message: Buffer) // Should this be corked?
|
|
186
|
+
target js async """
|
|
187
|
+
if(self_.internalJsApp_) self_.internalJsApp_.publish(topic_, message_.buffer, true);
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
publishText(topic: String, message: String) // Should this be corked?
|
|
191
|
+
target js async """
|
|
192
|
+
if(self_.internalJsApp_) self_.internalJsApp_.publish(topic_, message_, false);
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
extend self[T]: WebRequest[T] {
|
|
198
|
+
|
|
199
|
+
readMethod(): String {
|
|
200
|
+
self.internalMethod
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
readPath(): String {
|
|
204
|
+
self.internalPath
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
readRawQueryString(): String {
|
|
208
|
+
self.internalQuery
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
readQuery(name: String): Option[String]
|
|
212
|
+
target node sync """
|
|
213
|
+
if(self_.internalQuery_.length === 0) return ff_core_Option.None();
|
|
214
|
+
const ps = self_.internalQuery_.split('&');
|
|
215
|
+
const n = name_ + '=';
|
|
216
|
+
const r = ps.find(p => p === name_ || p.startsWith(n));
|
|
217
|
+
if(r == null) return ff_core_Option.None();
|
|
218
|
+
return ff_core_Option.Some(decodeURIComponent(r.slice(name_.length + 1)));
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
readHeader(lowerCaseName: String): Option[String] {
|
|
222
|
+
self.internalHeaders.get(lowerCaseName)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
eachHeader(body: (String, String) => Unit) {
|
|
226
|
+
self.internalHeaders.each {body(_, _)}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
extend self[T]: WebRequest[T] {
|
|
232
|
+
|
|
233
|
+
writeStatus(codeAndMessage: String) {
|
|
234
|
+
self.internalResponseStatus = codeAndMessage
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
writeHeader(name: String, value: String) {
|
|
238
|
+
self.internalResponseHeaders.set(name, value)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
writeContentSize(contentSize: Int) {
|
|
242
|
+
if(contentSize >= 0) {
|
|
243
|
+
self.internalContentSize = contentSize
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
writeText(data: String) {
|
|
248
|
+
self.internalResponseChunks.push(internalTextChunk(data))
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
writeLine(data: String) {
|
|
252
|
+
self.writeText(data + "\n")
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
writeBuffer(data: Buffer) {
|
|
256
|
+
self.internalResponseChunks.push(internalBufferChunk(data))
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
writeStream(data: Stream[Buffer]) {
|
|
260
|
+
data.each {self.writeBuffer(_)}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
extend self[T]: WebRequest[T] {
|
|
266
|
+
|
|
267
|
+
publishBuffer(topic: String, message: Buffer) // Should this be corked?
|
|
268
|
+
target js sync """
|
|
269
|
+
self_.internalJsApp_.publish(topic_, message_.buffer, true);
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
publishText(topic: String, message: String) // Should this be corked?
|
|
273
|
+
target js sync """
|
|
274
|
+
self_.internalJsApp_.publish(topic_, message_, false);
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
extend self[T]: WebRequest[T] {
|
|
280
|
+
|
|
281
|
+
closeConnection() {
|
|
282
|
+
self.internalCloseConnection = True
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
readRemoteAddress(): String
|
|
286
|
+
target js sync """
|
|
287
|
+
return new TextDecoder().decode(self_.internalJsResponse_.getRemoteAddressAsText())
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
readProxiedRemoteAddress(): String
|
|
291
|
+
target js sync """
|
|
292
|
+
return new TextDecoder().decode(self_.internalJsResponse_.getProxiedRemoteAddressAsText())
|
|
293
|
+
"""
|
|
294
|
+
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
extend self: WebRequest[WebResponse] {
|
|
298
|
+
|
|
299
|
+
flush()
|
|
300
|
+
target js async """
|
|
301
|
+
if(!self_.internalResolveFlush_) {
|
|
302
|
+
self_.internalResolveFlush_ = () => {};
|
|
303
|
+
self_.internalJsResponse_.cork(() => {
|
|
304
|
+
self_.internalJsResponse_.writeStatus(self_.internalResponseStatus_);
|
|
305
|
+
self_.internalResponseHeaders_.forEach((value, key) =>
|
|
306
|
+
self_.internalJsResponse_.writeHeader(key, value)
|
|
307
|
+
);
|
|
308
|
+
self_.internalJsResponse_.onWritable(() => {
|
|
309
|
+
self_.internalResolveFlush_();
|
|
310
|
+
return true;
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
while(self_.internalResponseChunks_.array.length !== 0) {
|
|
315
|
+
await new Promise((resolve, reject) => {
|
|
316
|
+
self_.internalJsResponse_.cork(() => {
|
|
317
|
+
let backpressure = false;
|
|
318
|
+
let i = 0;
|
|
319
|
+
for(i = 0; i < self_.internalResponseChunks_.array.length && !backpressure; i++) {
|
|
320
|
+
let chunk = self_.internalResponseChunks_.array[i];
|
|
321
|
+
if(chunk && (chunk.byteLength || chunk.length)) {
|
|
322
|
+
if(typeof chunk !== 'string') {
|
|
323
|
+
chunk = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
324
|
+
} else if(self_.internalContentSize_ >= 0) {
|
|
325
|
+
chunk = new TextEncoder().encode(chunk);
|
|
326
|
+
}
|
|
327
|
+
if(self_.internalContentSize_ >= 0) {
|
|
328
|
+
const lastOffset = self_.internalJsResponse_.getWriteOffset();
|
|
329
|
+
const [ok, responded] = self_.internalJsResponse_.tryEnd(chunk, self_.internalContentSize_);
|
|
330
|
+
if(responded) self_.internalResponded_ = true;
|
|
331
|
+
if(!ok) {
|
|
332
|
+
const written = self_.internalJsResponse_.getWriteOffset() - lastOffset;
|
|
333
|
+
backpressure = true;
|
|
334
|
+
self_.internalResponseChunks_.array[i] =
|
|
335
|
+
new Uint8Array(chunk.buffer, chunk.byteOffset + written, chunk.byteLength - written);
|
|
336
|
+
i -= 1;
|
|
337
|
+
}
|
|
338
|
+
} else {
|
|
339
|
+
backpressure = !self_.internalJsResponse_.write(chunk);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if(i > 0) self_.internalResponseChunks_.array.splice(0, i);
|
|
344
|
+
if(!backpressure) {
|
|
345
|
+
resolve();
|
|
346
|
+
} else {
|
|
347
|
+
self_.internalResolveFlush_ = resolve;
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
"""
|
|
353
|
+
|
|
354
|
+
flushText(data: String) {
|
|
355
|
+
self.writeText(data)
|
|
356
|
+
self.flush()
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
flushBuffer(data: Buffer) {
|
|
360
|
+
self.writeBuffer(data)
|
|
361
|
+
self.flush()
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
flushStream(data: Stream[Buffer]) {
|
|
365
|
+
data.each {self.flushBuffer(_)}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
extend self: WebRequest[WebResponse] {
|
|
371
|
+
|
|
372
|
+
readText(encoding: String = "utf8"): String {
|
|
373
|
+
self.readStream(1073741823).toString(encoding)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
readBuffer(): Buffer {
|
|
377
|
+
self.readStream(1073741823).toBuffer()
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
readStream(maxPendingBytes: Int = 65536): Stream[Buffer]
|
|
381
|
+
target js async """
|
|
382
|
+
const front = [];
|
|
383
|
+
const back = [];
|
|
384
|
+
let pendingBytes = 0
|
|
385
|
+
let closed = false;
|
|
386
|
+
let callResolve = null;
|
|
387
|
+
let callReject = null;
|
|
388
|
+
self_.internalJsResponse_.onData((chunk, isLast) => {
|
|
389
|
+
if(closed) return;
|
|
390
|
+
if(isLast) closed = true;
|
|
391
|
+
else chunk = cunk.slice();
|
|
392
|
+
pendingBytes += chunk.byteLength;
|
|
393
|
+
back.push(new DataView(chunk));
|
|
394
|
+
if(pendingBytes >= maxPendingBytes_) self_.internalJsResponse_.pause();
|
|
395
|
+
if(callResolve) callResolve();
|
|
396
|
+
});
|
|
397
|
+
const abort = () => {
|
|
398
|
+
$task.controller.signal.removeEventListener('abort', abort);
|
|
399
|
+
if(callReject) callReject();
|
|
400
|
+
}
|
|
401
|
+
$task.controller.signal.addEventListener('abort', abort);
|
|
402
|
+
return ff_core_Stream.Stream(
|
|
403
|
+
async () => {
|
|
404
|
+
while(true) {
|
|
405
|
+
ff_core_Task.Task_throwIfAborted($task);
|
|
406
|
+
if(front.length === 0) {
|
|
407
|
+
while(back.length !== 0) front.push(back.pop());
|
|
408
|
+
}
|
|
409
|
+
if(front.length !== 0) {
|
|
410
|
+
const chunk = front.pop();
|
|
411
|
+
const paused = pendingBytes >= maxPendingBytes_;
|
|
412
|
+
pendingBytes -= chunk.byteLength;
|
|
413
|
+
if(!closed && paused && pendingBytes < maxPendingBytes_) self_.internalJsResponse_.unpause();
|
|
414
|
+
return ff_core_Option.Some(chunk);
|
|
415
|
+
} else if(closed) {
|
|
416
|
+
return ff_core_Option.None();
|
|
417
|
+
} else {
|
|
418
|
+
await new Promise((resolve, reject) => {
|
|
419
|
+
callResolve = resolve;
|
|
420
|
+
callReject = reject;
|
|
421
|
+
});
|
|
422
|
+
callResolve = null;
|
|
423
|
+
callReject = null;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
async () => {
|
|
428
|
+
closed = true;
|
|
429
|
+
ff_core_Task.Task_throwIfAborted($task);
|
|
430
|
+
if(callResolve) callResolve();
|
|
431
|
+
}
|
|
432
|
+
)
|
|
433
|
+
"""
|
|
434
|
+
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
extend self[T]: WebRequest[T] {
|
|
438
|
+
|
|
439
|
+
needsWebSocket(): Bool
|
|
440
|
+
target js sync """
|
|
441
|
+
return self_.internalJsContext_ != null;
|
|
442
|
+
"""
|
|
443
|
+
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
extend self: WebRequest[WebResponse] {
|
|
447
|
+
|
|
448
|
+
openWebSocket(): WebSocketConnection
|
|
449
|
+
target js async """
|
|
450
|
+
ff_core_Task.Task_throwIfAborted($task);
|
|
451
|
+
const userData = {};
|
|
452
|
+
const wrapper = {front: [], back: [], listeners: [], task: $task};
|
|
453
|
+
const promise = new Promise((resolve, reject) => {
|
|
454
|
+
const abort = () => {wrapper.webSocket = null; reject()};
|
|
455
|
+
userData.onOpen = ws => {wrapper.webSocket = ws; resolve(wrapper)};
|
|
456
|
+
userData.onClose = () => {
|
|
457
|
+
$task.controller.signal.removeEventListener('abort', abort);
|
|
458
|
+
abort();
|
|
459
|
+
for(let i = 0; i < wrapper.listeners.length; i++) wrapper.listeners[i]();
|
|
460
|
+
wrapper.listeners.length = 0;
|
|
461
|
+
};
|
|
462
|
+
userData.onMessage = (message, isBinary) => {
|
|
463
|
+
wrapper.back.push(isBinary ? new DataView(message) : new TextDecoder().decode(message));
|
|
464
|
+
for(let i = 0; i < wrapper.listeners.length; i++) wrapper.listeners[i]();
|
|
465
|
+
wrapper.listeners.length = 0;
|
|
466
|
+
};
|
|
467
|
+
$task.controller.signal.addEventListener('abort', abort);
|
|
468
|
+
});
|
|
469
|
+
self_.internalJsResponse_.cork(() => {
|
|
470
|
+
ff_core_Task.Task_throwIfAborted($task);
|
|
471
|
+
self_.internalJsResponse_.upgrade(
|
|
472
|
+
userData,
|
|
473
|
+
self_.internalHeaders_.get('sec-websocket-key'),
|
|
474
|
+
self_.internalHeaders_.get('sec-websocket-protocol'),
|
|
475
|
+
self_.internalHeaders_.get('sec-websocket-extensions'),
|
|
476
|
+
self_.internalJsContext_
|
|
477
|
+
)
|
|
478
|
+
});
|
|
479
|
+
return await promise;
|
|
480
|
+
"""
|
|
481
|
+
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
extend self: WebSocketConnection {
|
|
485
|
+
|
|
486
|
+
readText(encoding: String = "utf8"): Option[String] {
|
|
487
|
+
self.readAny {_} {_.toString(encoding)}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
readBuffer(): Option[Buffer] {
|
|
491
|
+
self.readAny {_.toBuffer()} {_}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
readAny[T](fromText: String => T, fromBuffer: Buffer => T): Option[T]
|
|
495
|
+
target node async """
|
|
496
|
+
ff_core_Task.Task_throwIfAborted($task);
|
|
497
|
+
while(self_.front.length === 0) {
|
|
498
|
+
while(self_.back.length !== 0) {
|
|
499
|
+
self_.front.push(self_.back.pop());
|
|
500
|
+
}
|
|
501
|
+
if(self_.front.length !== 0) break;
|
|
502
|
+
if(self_.webSocket == null) return ff_core_Option.None();
|
|
503
|
+
let abort = null;
|
|
504
|
+
try {
|
|
505
|
+
await new Promise((resolve, reject) => {
|
|
506
|
+
self_.listeners.push(resolve);
|
|
507
|
+
if($task !== self_.task) {
|
|
508
|
+
abort = () => {
|
|
509
|
+
self_.listeners = self_.listeners.filter(l => l != resolve);
|
|
510
|
+
reject();
|
|
511
|
+
};
|
|
512
|
+
$task.controller.signal.addEventListener('abort', abort);
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
} finally {
|
|
516
|
+
if($task !== self_.task) {
|
|
517
|
+
$task.controller.signal.removeEventListener('abort', abort);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
const data = self_.front.pop();
|
|
522
|
+
if(typeof data === 'string') return ff_core_Option.Some(await fromText_(data));
|
|
523
|
+
return ff_core_Option.Some(await fromBuffer_(data));
|
|
524
|
+
"""
|
|
525
|
+
|
|
526
|
+
readStream(): Stream[Buffer] {
|
|
527
|
+
Stream {self.readBuffer()} {self.close()}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
writeBuffer(message: Buffer)
|
|
531
|
+
target node async """
|
|
532
|
+
await internalSend_$(self_, () => self_.webSocket.send(message_.buffer, true) !== 2, $task);
|
|
533
|
+
"""
|
|
534
|
+
|
|
535
|
+
writeText(message: String)
|
|
536
|
+
target node async """
|
|
537
|
+
await internalSend_$(self_, () => self_.webSocket.send(message_, false) !== 2, $task);
|
|
538
|
+
"""
|
|
539
|
+
|
|
540
|
+
publishBuffer(topic: String, message: Buffer)
|
|
541
|
+
target node async """
|
|
542
|
+
await internalSend_$(self_, () => self_.webSocket.publish(topic_, message_.buffer, true), $task);
|
|
543
|
+
"""
|
|
544
|
+
|
|
545
|
+
publishText(topic: String, message: String)
|
|
546
|
+
target node async """
|
|
547
|
+
await internalSend_$(self_, () => self_.webSocket.publish(topic_, message_, false), $task);
|
|
548
|
+
"""
|
|
549
|
+
|
|
550
|
+
subscribe(topic: String)
|
|
551
|
+
target node async """
|
|
552
|
+
if(self_.webSocket == null) throw new Error("WebSocket is closed");
|
|
553
|
+
self_.webSocket.subscribe(topic_);
|
|
554
|
+
"""
|
|
555
|
+
|
|
556
|
+
unsubscribe(topic: String)
|
|
557
|
+
target node async """
|
|
558
|
+
if(self_.webSocket == null) throw new Error("WebSocket is closed");
|
|
559
|
+
self_.webSocket.unsubscribe(topic_);
|
|
560
|
+
"""
|
|
561
|
+
|
|
562
|
+
isSubscribedTo(topic: String): Bool
|
|
563
|
+
target node async """
|
|
564
|
+
if(self_.webSocket == null) throw new Error("WebSocket is closed");
|
|
565
|
+
return self_.webSocket.isSubscribed(topic_);
|
|
566
|
+
"""
|
|
567
|
+
|
|
568
|
+
subscriptions(): Array[String]
|
|
569
|
+
target node async """
|
|
570
|
+
if(self_.webSocket == null) throw new Error("WebSocket is closed");
|
|
571
|
+
return self_.webSocket.getTopics();
|
|
572
|
+
"""
|
|
573
|
+
|
|
574
|
+
close(): Unit
|
|
575
|
+
target node async """
|
|
576
|
+
if(self_.webSocket != null) {
|
|
577
|
+
self_.webSocket.close();
|
|
578
|
+
self_.webSocket = null;
|
|
579
|
+
}
|
|
580
|
+
"""
|
|
581
|
+
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
internalSend[T](self: WebSocketConnection, send: () => Bool): JsValue
|
|
585
|
+
target node async """
|
|
586
|
+
let abort = null;
|
|
587
|
+
try {
|
|
588
|
+
await new Promise((resolve, reject) => {
|
|
589
|
+
if(self_.webSocket == null) new Error("WebSocket is closed");
|
|
590
|
+
else self_.webSocket.cork(() => {
|
|
591
|
+
if($task !== self_.task) {
|
|
592
|
+
abort = () => {
|
|
593
|
+
self_.listeners = self_.listeners.filter(l => l != resolve);
|
|
594
|
+
reject();
|
|
595
|
+
};
|
|
596
|
+
$task.controller.signal.addEventListener('abort', abort);
|
|
597
|
+
}
|
|
598
|
+
if(self_.webSocket == null) {
|
|
599
|
+
reject(new Error("WebSocket is closed"));
|
|
600
|
+
} else if(!send_()) {
|
|
601
|
+
reject(new Error("Dropped message due to backpressure"));
|
|
602
|
+
} else {
|
|
603
|
+
resolve();
|
|
604
|
+
}
|
|
605
|
+
})
|
|
606
|
+
});
|
|
607
|
+
} finally {
|
|
608
|
+
if($task !== self_.task) {
|
|
609
|
+
$task.controller.signal.removeEventListener('abort', abort);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
"""
|
|
613
|
+
|
|
614
|
+
internalArray[T](): Array[T] {
|
|
615
|
+
Array.new()
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
internalTextChunk(data: String): JsValue
|
|
619
|
+
target js sync """
|
|
620
|
+
return data_;
|
|
621
|
+
"""
|
|
622
|
+
|
|
623
|
+
internalBufferChunk(data: Buffer): JsValue
|
|
624
|
+
target js sync """
|
|
625
|
+
return data_;
|
|
626
|
+
"""
|
|
627
|
+
|
|
628
|
+
internalChunksToChunk(chunks: Array[JsValue]): JsValue
|
|
629
|
+
target js sync """
|
|
630
|
+
const chunks = chunks_.array;
|
|
631
|
+
if(chunks.length === 0) return;
|
|
632
|
+
const firstIsString = typeof chunks[0] === 'string';
|
|
633
|
+
if(firstIsString && chunks.length === 1) return chunks[0];
|
|
634
|
+
if(firstIsString && chunks.every(c => typeof c === 'string')) {
|
|
635
|
+
let result = chunks[0];
|
|
636
|
+
for(let i = 1; i < chunks.length; i++) {
|
|
637
|
+
result += chunks[i];
|
|
638
|
+
}
|
|
639
|
+
return result;
|
|
640
|
+
}
|
|
641
|
+
let totalLength = 0;
|
|
642
|
+
for(let i = 0; i < chunks.length; i++) {
|
|
643
|
+
if(typeof chunks[i] === 'string') chunks[i] = new TextEncoder().encode(chunks[i]);
|
|
644
|
+
totalLength += chunks[i].byteLength;
|
|
645
|
+
}
|
|
646
|
+
const result = new Uint8Array(totalLength);
|
|
647
|
+
let offset = 0;
|
|
648
|
+
for(let i = 0; i < chunks.length; i++) {
|
|
649
|
+
const chunk = chunks[i];
|
|
650
|
+
const uint8Array = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
651
|
+
result.set(uint8Array, offset);
|
|
652
|
+
offset += chunk.byteLength;
|
|
653
|
+
}
|
|
654
|
+
return result;
|
|
655
|
+
"""
|
|
656
|
+
|
|
657
|
+
parseMultipartFields(data: Buffer, contentType: String): Option[Array[MultipartField]]
|
|
658
|
+
target js sync """
|
|
659
|
+
const parts = getParts(data_, contentType_);
|
|
660
|
+
return parts == null ? ff_core_Option.None() : ff_core_Option.Some(parts);
|
|
661
|
+
"""
|
|
662
|
+
|
|
663
|
+
extend self: MultipartField {
|
|
664
|
+
|
|
665
|
+
data(): Buffer
|
|
666
|
+
target js sync """
|
|
667
|
+
return new DataView(self_.data);
|
|
668
|
+
"""
|
|
669
|
+
|
|
670
|
+
name(): String
|
|
671
|
+
target js sync """
|
|
672
|
+
return self_.name;
|
|
673
|
+
"""
|
|
674
|
+
|
|
675
|
+
type(): Option[String]
|
|
676
|
+
target js sync """
|
|
677
|
+
return self_.type == null ? ff_core_Option.None() : ff_core_Option.Some(self_.type);
|
|
678
|
+
"""
|
|
679
|
+
|
|
680
|
+
filename(): Option[String]
|
|
681
|
+
target js sync """
|
|
682
|
+
return self_.filename == null ? ff_core_Option.None() : ff_core_Option.Some(self_.filename);
|
|
683
|
+
"""
|
|
684
|
+
|
|
685
|
+
}
|