@bytecodealliance/preview2-shim 0.18.1 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/cli.d.ts +16 -0
- package/dist/browser/cli.js +109 -0
- package/dist/browser/clocks.d.ts +3 -0
- package/dist/browser/clocks.js +36 -0
- package/dist/browser/config.d.ts +2 -0
- package/{lib → dist}/browser/config.js +2 -4
- package/dist/browser/environment.d.ts +5 -0
- package/dist/browser/environment.js +24 -0
- package/dist/browser/filesystem.d.ts +132 -0
- package/dist/browser/filesystem.js +464 -0
- package/dist/browser/http.d.ts +4 -0
- package/dist/browser/http.js +643 -0
- package/dist/browser/index.d.ts +7 -0
- package/dist/browser/index.js +7 -0
- package/dist/browser/io.d.ts +58 -0
- package/dist/browser/io.js +205 -0
- package/dist/browser/random.d.ts +4 -0
- package/dist/browser/random.js +49 -0
- package/dist/browser/sockets.d.ts +8 -0
- package/dist/browser/sockets.js +66 -0
- package/dist/common/instantiation.d.ts +97 -0
- package/dist/common/instantiation.js +227 -0
- package/dist/io/calls.d.ts +91 -0
- package/{lib → dist}/io/calls.js +4 -19
- package/dist/io/worker-http.d.ts +17 -0
- package/dist/io/worker-http.js +192 -0
- package/dist/io/worker-io.d.ts +84 -0
- package/dist/io/worker-io.js +375 -0
- package/dist/io/worker-socket-tcp.d.ts +34 -0
- package/dist/io/worker-socket-tcp.js +251 -0
- package/dist/io/worker-socket-udp.d.ts +54 -0
- package/dist/io/worker-socket-udp.js +492 -0
- package/dist/io/worker-sockets.d.ts +32 -0
- package/dist/io/worker-sockets.js +265 -0
- package/dist/io/worker-thread.d.ts +73 -0
- package/dist/io/worker-thread.js +791 -0
- package/dist/nodejs/cli.d.ts +30 -0
- package/dist/nodejs/cli.js +86 -0
- package/dist/nodejs/clocks.d.ts +3 -0
- package/dist/nodejs/clocks.js +61 -0
- package/dist/nodejs/filesystem.d.ts +113 -0
- package/dist/nodejs/filesystem.js +758 -0
- package/dist/nodejs/http.d.ts +82 -0
- package/dist/nodejs/http.js +674 -0
- package/dist/nodejs/index.d.ts +7 -0
- package/dist/nodejs/index.js +7 -0
- package/dist/nodejs/io.d.ts +1 -0
- package/dist/nodejs/io.js +1 -0
- package/dist/nodejs/random.d.ts +4 -0
- package/dist/nodejs/random.js +42 -0
- package/dist/nodejs/sockets.d.ts +21 -0
- package/dist/nodejs/sockets.js +482 -0
- package/dist/synckit/index.d.ts +2 -0
- package/dist/synckit/index.js +117 -0
- package/package.json +26 -13
- package/types/instantiation.d.ts +7 -7
- package/lib/browser/cli.js +0 -135
- package/lib/browser/clocks.js +0 -39
- package/lib/browser/environment.js +0 -29
- package/lib/browser/filesystem.js +0 -503
- package/lib/browser/http.js +0 -707
- package/lib/browser/index.js +0 -9
- package/lib/browser/io.js +0 -262
- package/lib/browser/random.js +0 -56
- package/lib/browser/sockets.js +0 -88
- package/lib/common/instantiation.js +0 -253
- package/lib/io/worker-http.js +0 -206
- package/lib/io/worker-io.js +0 -468
- package/lib/io/worker-socket-tcp.js +0 -331
- package/lib/io/worker-socket-udp.js +0 -587
- package/lib/io/worker-sockets.js +0 -364
- package/lib/io/worker-thread.js +0 -1051
- package/lib/nodejs/cli.js +0 -109
- package/lib/nodejs/clocks.js +0 -67
- package/lib/nodejs/filesystem.js +0 -829
- package/lib/nodejs/http.js +0 -764
- package/lib/nodejs/index.js +0 -9
- package/lib/nodejs/io.js +0 -1
- package/lib/nodejs/random.js +0 -49
- package/lib/nodejs/sockets.js +0 -565
- package/lib/synckit/index.js +0 -127
package/lib/nodejs/http.js
DELETED
|
@@ -1,764 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
FUTURE_DISPOSE,
|
|
3
|
-
FUTURE_SUBSCRIBE,
|
|
4
|
-
FUTURE_TAKE_VALUE,
|
|
5
|
-
HTTP_CREATE_REQUEST,
|
|
6
|
-
HTTP_OUTGOING_BODY_DISPOSE,
|
|
7
|
-
HTTP_OUTPUT_STREAM_FINISH,
|
|
8
|
-
HTTP_SERVER_CLEAR_OUTGOING_RESPONSE,
|
|
9
|
-
HTTP_SERVER_SET_OUTGOING_RESPONSE,
|
|
10
|
-
HTTP_SERVER_START,
|
|
11
|
-
HTTP_SERVER_STOP,
|
|
12
|
-
HTTP_SERVER_GET_ADDRESS,
|
|
13
|
-
OUTPUT_STREAM_CREATE,
|
|
14
|
-
OUTPUT_STREAM_DISPOSE,
|
|
15
|
-
} from "../io/calls.js";
|
|
16
|
-
import {
|
|
17
|
-
earlyDispose,
|
|
18
|
-
inputStreamCreate,
|
|
19
|
-
ioCall,
|
|
20
|
-
outputStreamCreate,
|
|
21
|
-
pollableCreate,
|
|
22
|
-
registerDispose,
|
|
23
|
-
registerIncomingHttpHandler,
|
|
24
|
-
} from "../io/worker-io.js";
|
|
25
|
-
import { HTTP } from "../io/calls.js";
|
|
26
|
-
|
|
27
|
-
import * as http from "node:http";
|
|
28
|
-
const { validateHeaderName = () => {}, validateHeaderValue = () => {} } = http;
|
|
29
|
-
|
|
30
|
-
const symbolDispose = Symbol.dispose || Symbol.for("dispose");
|
|
31
|
-
export const _forbiddenHeaders = new Set(["connection", "keep-alive", "host"]);
|
|
32
|
-
|
|
33
|
-
class IncomingBody {
|
|
34
|
-
#finished = false;
|
|
35
|
-
#stream = undefined;
|
|
36
|
-
stream() {
|
|
37
|
-
if (!this.#stream) {
|
|
38
|
-
throw undefined;
|
|
39
|
-
}
|
|
40
|
-
const stream = this.#stream;
|
|
41
|
-
this.#stream = null;
|
|
42
|
-
return stream;
|
|
43
|
-
}
|
|
44
|
-
static finish(incomingBody) {
|
|
45
|
-
if (incomingBody.#finished) {
|
|
46
|
-
throw new Error("incoming body already finished");
|
|
47
|
-
}
|
|
48
|
-
incomingBody.#finished = true;
|
|
49
|
-
return futureTrailersCreate();
|
|
50
|
-
}
|
|
51
|
-
[symbolDispose]() {}
|
|
52
|
-
static _create(streamId) {
|
|
53
|
-
const incomingBody = new IncomingBody();
|
|
54
|
-
incomingBody.#stream = inputStreamCreate(HTTP, streamId);
|
|
55
|
-
return incomingBody;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
const incomingBodyCreate = IncomingBody._create;
|
|
59
|
-
delete IncomingBody._create;
|
|
60
|
-
|
|
61
|
-
class IncomingRequest {
|
|
62
|
-
#method;
|
|
63
|
-
#pathWithQuery;
|
|
64
|
-
#scheme;
|
|
65
|
-
#authority;
|
|
66
|
-
#headers;
|
|
67
|
-
#streamId;
|
|
68
|
-
method() {
|
|
69
|
-
return this.#method;
|
|
70
|
-
}
|
|
71
|
-
pathWithQuery() {
|
|
72
|
-
return this.#pathWithQuery;
|
|
73
|
-
}
|
|
74
|
-
scheme() {
|
|
75
|
-
return this.#scheme;
|
|
76
|
-
}
|
|
77
|
-
authority() {
|
|
78
|
-
return this.#authority;
|
|
79
|
-
}
|
|
80
|
-
headers() {
|
|
81
|
-
return this.#headers;
|
|
82
|
-
}
|
|
83
|
-
consume() {
|
|
84
|
-
return incomingBodyCreate(this.#streamId);
|
|
85
|
-
}
|
|
86
|
-
[symbolDispose]() {}
|
|
87
|
-
static _create(method, pathWithQuery, scheme, authority, headers, streamId) {
|
|
88
|
-
const incomingRequest = new IncomingRequest();
|
|
89
|
-
incomingRequest.#method = method;
|
|
90
|
-
incomingRequest.#pathWithQuery = pathWithQuery;
|
|
91
|
-
incomingRequest.#scheme = scheme;
|
|
92
|
-
incomingRequest.#authority = authority;
|
|
93
|
-
incomingRequest.#headers = headers;
|
|
94
|
-
incomingRequest.#streamId = streamId;
|
|
95
|
-
return incomingRequest;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
const incomingRequestCreate = IncomingRequest._create;
|
|
99
|
-
delete IncomingRequest._create;
|
|
100
|
-
|
|
101
|
-
class FutureTrailers {
|
|
102
|
-
#requested = false;
|
|
103
|
-
subscribe() {
|
|
104
|
-
return pollableCreate(0, this);
|
|
105
|
-
}
|
|
106
|
-
get() {
|
|
107
|
-
if (this.#requested) {
|
|
108
|
-
return { tag: "err" };
|
|
109
|
-
}
|
|
110
|
-
this.#requested = true;
|
|
111
|
-
return {
|
|
112
|
-
tag: "ok",
|
|
113
|
-
val: {
|
|
114
|
-
tag: "ok",
|
|
115
|
-
val: undefined,
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
static _create() {
|
|
120
|
-
const res = new FutureTrailers();
|
|
121
|
-
return res;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
const futureTrailersCreate = FutureTrailers._create;
|
|
125
|
-
delete FutureTrailers._create;
|
|
126
|
-
|
|
127
|
-
class OutgoingResponse {
|
|
128
|
-
#body;
|
|
129
|
-
/** @type {number} */ #statusCode = 200;
|
|
130
|
-
/** @type {Fields} */ #headers;
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* @param {number} statusCode
|
|
134
|
-
* @param {Fields} headers
|
|
135
|
-
*/
|
|
136
|
-
constructor(headers) {
|
|
137
|
-
fieldsLock(headers);
|
|
138
|
-
this.#headers = headers;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
statusCode() {
|
|
142
|
-
return this.#statusCode;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
setStatusCode(statusCode) {
|
|
146
|
-
this.#statusCode = statusCode;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
headers() {
|
|
150
|
-
return this.#headers;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
body() {
|
|
154
|
-
let contentLengthValues = this.#headers.get("content-length");
|
|
155
|
-
let contentLength;
|
|
156
|
-
if (contentLengthValues.length > 0) {
|
|
157
|
-
contentLength = Number(new TextDecoder().decode(contentLengthValues[0]));
|
|
158
|
-
}
|
|
159
|
-
this.#body = outgoingBodyCreate(contentLength);
|
|
160
|
-
return this.#body;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
static _body(outgoingResponse) {
|
|
164
|
-
return outgoingResponse.#body;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const outgoingResponseBody = OutgoingResponse._body;
|
|
169
|
-
delete OutgoingResponse._body;
|
|
170
|
-
|
|
171
|
-
class ResponseOutparam {
|
|
172
|
-
#setListener;
|
|
173
|
-
static set(param, response) {
|
|
174
|
-
param.#setListener(response);
|
|
175
|
-
}
|
|
176
|
-
static _create(setListener) {
|
|
177
|
-
const responseOutparam = new ResponseOutparam();
|
|
178
|
-
responseOutparam.#setListener = setListener;
|
|
179
|
-
return responseOutparam;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
const responseOutparamCreate = ResponseOutparam._create;
|
|
183
|
-
delete ResponseOutparam._create;
|
|
184
|
-
|
|
185
|
-
const defaultHttpTimeout = 600_000_000_000n;
|
|
186
|
-
|
|
187
|
-
class RequestOptions {
|
|
188
|
-
#connectTimeout = defaultHttpTimeout;
|
|
189
|
-
#firstByteTimeout = defaultHttpTimeout;
|
|
190
|
-
#betweenBytesTimeout = defaultHttpTimeout;
|
|
191
|
-
connectTimeout() {
|
|
192
|
-
return this.#connectTimeout;
|
|
193
|
-
}
|
|
194
|
-
setConnectTimeout(duration) {
|
|
195
|
-
this.#connectTimeout = duration;
|
|
196
|
-
}
|
|
197
|
-
firstByteTimeout() {
|
|
198
|
-
return this.#firstByteTimeout;
|
|
199
|
-
}
|
|
200
|
-
setFirstByteTimeout(duration) {
|
|
201
|
-
this.#firstByteTimeout = duration;
|
|
202
|
-
}
|
|
203
|
-
betweenBytesTimeout() {
|
|
204
|
-
return this.#betweenBytesTimeout;
|
|
205
|
-
}
|
|
206
|
-
setBetweenBytesTimeout(duration) {
|
|
207
|
-
this.#betweenBytesTimeout = duration;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
class OutgoingRequest {
|
|
212
|
-
/** @type {Method} */ #method = { tag: "get" };
|
|
213
|
-
/** @type {Scheme | undefined} */ #scheme = undefined;
|
|
214
|
-
/** @type {string | undefined} */ #pathWithQuery = undefined;
|
|
215
|
-
/** @type {string | undefined} */ #authority = undefined;
|
|
216
|
-
/** @type {Fields} */ #headers;
|
|
217
|
-
/** @type {OutgoingBody} */ #body;
|
|
218
|
-
#bodyRequested = false;
|
|
219
|
-
constructor(headers) {
|
|
220
|
-
fieldsLock(headers);
|
|
221
|
-
this.#headers = headers;
|
|
222
|
-
let contentLengthValues = this.#headers.get("content-length");
|
|
223
|
-
if (contentLengthValues.length === 0) {
|
|
224
|
-
contentLengthValues = this.#headers.get("Content-Length");
|
|
225
|
-
}
|
|
226
|
-
let contentLength;
|
|
227
|
-
if (contentLengthValues.length > 0) {
|
|
228
|
-
contentLength = Number(new TextDecoder().decode(contentLengthValues[0]));
|
|
229
|
-
}
|
|
230
|
-
this.#body = outgoingBodyCreate(contentLength);
|
|
231
|
-
}
|
|
232
|
-
body() {
|
|
233
|
-
if (this.#bodyRequested) {
|
|
234
|
-
throw new Error("Body already requested");
|
|
235
|
-
}
|
|
236
|
-
this.#bodyRequested = true;
|
|
237
|
-
return this.#body;
|
|
238
|
-
}
|
|
239
|
-
method() {
|
|
240
|
-
return this.#method;
|
|
241
|
-
}
|
|
242
|
-
setMethod(method) {
|
|
243
|
-
if (method.tag === "other" && !method.val.match(/^[a-zA-Z-]+$/)) {
|
|
244
|
-
throw undefined;
|
|
245
|
-
}
|
|
246
|
-
this.#method = method;
|
|
247
|
-
}
|
|
248
|
-
pathWithQuery() {
|
|
249
|
-
return this.#pathWithQuery;
|
|
250
|
-
}
|
|
251
|
-
setPathWithQuery(pathWithQuery) {
|
|
252
|
-
if (pathWithQuery && !pathWithQuery.match(/^[a-zA-Z0-9.\-_~!$&'()*+,;=:@%?/]+$/)) {
|
|
253
|
-
throw undefined;
|
|
254
|
-
}
|
|
255
|
-
this.#pathWithQuery = pathWithQuery;
|
|
256
|
-
}
|
|
257
|
-
scheme() {
|
|
258
|
-
return this.#scheme;
|
|
259
|
-
}
|
|
260
|
-
setScheme(scheme) {
|
|
261
|
-
if (scheme?.tag === "other" && !scheme.val.match(/^[a-zA-Z]+$/)) {
|
|
262
|
-
throw undefined;
|
|
263
|
-
}
|
|
264
|
-
this.#scheme = scheme;
|
|
265
|
-
}
|
|
266
|
-
authority() {
|
|
267
|
-
return this.#authority;
|
|
268
|
-
}
|
|
269
|
-
setAuthority(authority) {
|
|
270
|
-
if (authority) {
|
|
271
|
-
const [host, port, ...extra] = authority.split(":");
|
|
272
|
-
const portNum = Number(port);
|
|
273
|
-
if (
|
|
274
|
-
extra.length ||
|
|
275
|
-
(port !== undefined && (portNum.toString() !== port || portNum > 65535)) ||
|
|
276
|
-
!host.match(/^[a-zA-Z0-9-.]+$/)
|
|
277
|
-
) {
|
|
278
|
-
throw undefined;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
this.#authority = authority;
|
|
282
|
-
}
|
|
283
|
-
headers() {
|
|
284
|
-
return this.#headers;
|
|
285
|
-
}
|
|
286
|
-
[symbolDispose]() {}
|
|
287
|
-
static _handle(request, options) {
|
|
288
|
-
const connectTimeout = options?.connectTimeout();
|
|
289
|
-
const betweenBytesTimeout = options?.betweenBytesTimeout();
|
|
290
|
-
const firstByteTimeout = options?.firstByteTimeout();
|
|
291
|
-
const scheme = schemeString(request.#scheme);
|
|
292
|
-
// note: host header is automatically added by Node.js
|
|
293
|
-
const headers = [];
|
|
294
|
-
const decoder = new TextDecoder();
|
|
295
|
-
for (const [key, value] of request.#headers.entries()) {
|
|
296
|
-
headers.push([key, decoder.decode(value)]);
|
|
297
|
-
}
|
|
298
|
-
if (!request.#pathWithQuery) {
|
|
299
|
-
throw { tag: "HTTP-request-URI-invalid" };
|
|
300
|
-
}
|
|
301
|
-
return futureIncomingResponseCreate(
|
|
302
|
-
request.#method.val || request.#method.tag,
|
|
303
|
-
scheme,
|
|
304
|
-
request.#authority,
|
|
305
|
-
request.#pathWithQuery,
|
|
306
|
-
headers,
|
|
307
|
-
outgoingBodyOutputStreamId(request.#body),
|
|
308
|
-
connectTimeout,
|
|
309
|
-
betweenBytesTimeout,
|
|
310
|
-
firstByteTimeout,
|
|
311
|
-
);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
const outgoingRequestHandle = OutgoingRequest._handle;
|
|
316
|
-
delete OutgoingRequest._handle;
|
|
317
|
-
|
|
318
|
-
class OutgoingBody {
|
|
319
|
-
#outputStream = null;
|
|
320
|
-
#outputStreamId = null;
|
|
321
|
-
#contentLength = undefined;
|
|
322
|
-
#finalizer;
|
|
323
|
-
write() {
|
|
324
|
-
// can only call write once
|
|
325
|
-
const outputStream = this.#outputStream;
|
|
326
|
-
if (outputStream === null) {
|
|
327
|
-
throw undefined;
|
|
328
|
-
}
|
|
329
|
-
this.#outputStream = null;
|
|
330
|
-
return outputStream;
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* @param {OutgoingBody} body
|
|
334
|
-
* @param {Fields | undefined} trailers
|
|
335
|
-
*/
|
|
336
|
-
static finish(body, trailers) {
|
|
337
|
-
if (trailers) {
|
|
338
|
-
throw { tag: "internal-error", val: "trailers unsupported" };
|
|
339
|
-
}
|
|
340
|
-
// this will verify content length, and also verify not already finished
|
|
341
|
-
// throwing errors as appropriate
|
|
342
|
-
ioCall(HTTP_OUTPUT_STREAM_FINISH, body.#outputStreamId, null);
|
|
343
|
-
}
|
|
344
|
-
static _outputStreamId(outgoingBody) {
|
|
345
|
-
return outgoingBody.#outputStreamId;
|
|
346
|
-
}
|
|
347
|
-
static _create(contentLength) {
|
|
348
|
-
const outgoingBody = new OutgoingBody();
|
|
349
|
-
outgoingBody.#contentLength = contentLength;
|
|
350
|
-
outgoingBody.#outputStreamId = ioCall(
|
|
351
|
-
OUTPUT_STREAM_CREATE | HTTP,
|
|
352
|
-
null,
|
|
353
|
-
outgoingBody.#contentLength,
|
|
354
|
-
);
|
|
355
|
-
outgoingBody.#outputStream = outputStreamCreate(HTTP, outgoingBody.#outputStreamId);
|
|
356
|
-
outgoingBody.#finalizer = registerDispose(
|
|
357
|
-
outgoingBody,
|
|
358
|
-
null,
|
|
359
|
-
outgoingBody.#outputStreamId,
|
|
360
|
-
outgoingBodyDispose,
|
|
361
|
-
);
|
|
362
|
-
return outgoingBody;
|
|
363
|
-
}
|
|
364
|
-
[symbolDispose]() {
|
|
365
|
-
if (this.#finalizer) {
|
|
366
|
-
earlyDispose(this.#finalizer);
|
|
367
|
-
this.#finalizer = null;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
function outgoingBodyDispose(id) {
|
|
373
|
-
ioCall(HTTP_OUTGOING_BODY_DISPOSE, id, null);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
const outgoingBodyOutputStreamId = OutgoingBody._outputStreamId;
|
|
377
|
-
delete OutgoingBody._outputStreamId;
|
|
378
|
-
|
|
379
|
-
const outgoingBodyCreate = OutgoingBody._create;
|
|
380
|
-
delete OutgoingBody._create;
|
|
381
|
-
|
|
382
|
-
class IncomingResponse {
|
|
383
|
-
/** @type {Fields} */ #headers = undefined;
|
|
384
|
-
#status = 0;
|
|
385
|
-
/** @type {number} */ #bodyStream;
|
|
386
|
-
status() {
|
|
387
|
-
return this.#status;
|
|
388
|
-
}
|
|
389
|
-
headers() {
|
|
390
|
-
return this.#headers;
|
|
391
|
-
}
|
|
392
|
-
consume() {
|
|
393
|
-
if (this.#bodyStream === undefined) {
|
|
394
|
-
throw undefined;
|
|
395
|
-
}
|
|
396
|
-
const bodyStream = this.#bodyStream;
|
|
397
|
-
this.#bodyStream = undefined;
|
|
398
|
-
return bodyStream;
|
|
399
|
-
}
|
|
400
|
-
[symbolDispose]() {
|
|
401
|
-
if (this.#bodyStream) {
|
|
402
|
-
this.#bodyStream[symbolDispose]();
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
static _create(status, headers, bodyStreamId) {
|
|
406
|
-
const res = new IncomingResponse();
|
|
407
|
-
res.#status = status;
|
|
408
|
-
res.#headers = headers;
|
|
409
|
-
res.#bodyStream = incomingBodyCreate(bodyStreamId);
|
|
410
|
-
return res;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
const incomingResponseCreate = IncomingResponse._create;
|
|
415
|
-
delete IncomingResponse._create;
|
|
416
|
-
|
|
417
|
-
class FutureIncomingResponse {
|
|
418
|
-
#id;
|
|
419
|
-
#finalizer;
|
|
420
|
-
subscribe() {
|
|
421
|
-
return pollableCreate(ioCall(FUTURE_SUBSCRIBE | HTTP, this.#id, null), this);
|
|
422
|
-
}
|
|
423
|
-
get() {
|
|
424
|
-
const ret = ioCall(FUTURE_TAKE_VALUE | HTTP, this.#id, null);
|
|
425
|
-
if (ret === undefined) {
|
|
426
|
-
return undefined;
|
|
427
|
-
}
|
|
428
|
-
if (ret.tag === "ok" && ret.val.tag === "ok") {
|
|
429
|
-
const textEncoder = new TextEncoder();
|
|
430
|
-
const { status, headers, bodyStreamId } = ret.val.val;
|
|
431
|
-
ret.val.val = incomingResponseCreate(
|
|
432
|
-
status,
|
|
433
|
-
fieldsFromEntriesChecked(headers.map(([key, val]) => [key, textEncoder.encode(val)])),
|
|
434
|
-
bodyStreamId,
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
return ret;
|
|
438
|
-
}
|
|
439
|
-
static _create(
|
|
440
|
-
method,
|
|
441
|
-
scheme,
|
|
442
|
-
authority,
|
|
443
|
-
pathWithQuery,
|
|
444
|
-
headers,
|
|
445
|
-
body,
|
|
446
|
-
connectTimeout,
|
|
447
|
-
betweenBytesTimeout,
|
|
448
|
-
firstByteTimeout,
|
|
449
|
-
) {
|
|
450
|
-
const res = new FutureIncomingResponse();
|
|
451
|
-
res.#id = ioCall(HTTP_CREATE_REQUEST, null, {
|
|
452
|
-
method,
|
|
453
|
-
scheme,
|
|
454
|
-
authority,
|
|
455
|
-
pathWithQuery,
|
|
456
|
-
headers,
|
|
457
|
-
body,
|
|
458
|
-
connectTimeout,
|
|
459
|
-
betweenBytesTimeout,
|
|
460
|
-
firstByteTimeout,
|
|
461
|
-
});
|
|
462
|
-
res.#finalizer = registerDispose(res, null, res.#id, futureIncomingResponseDispose);
|
|
463
|
-
return res;
|
|
464
|
-
}
|
|
465
|
-
[symbolDispose]() {
|
|
466
|
-
if (this.#finalizer) {
|
|
467
|
-
earlyDispose(this.#finalizer);
|
|
468
|
-
this.#finalizer = null;
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
function futureIncomingResponseDispose(id) {
|
|
474
|
-
ioCall(FUTURE_DISPOSE | HTTP, id, null);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
const futureIncomingResponseCreate = FutureIncomingResponse._create;
|
|
478
|
-
delete FutureIncomingResponse._create;
|
|
479
|
-
|
|
480
|
-
class Fields {
|
|
481
|
-
#immutable = false;
|
|
482
|
-
/** @type {[string, Uint8Array[]][]} */ #entries = [];
|
|
483
|
-
/** @type {Map<string, [string, Uint8Array[]][]>} */ #table = new Map();
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* @param {[string, Uint8Array[][]][]} entries
|
|
487
|
-
*/
|
|
488
|
-
static fromList(entries) {
|
|
489
|
-
const fields = new Fields();
|
|
490
|
-
for (const [key, value] of entries) {
|
|
491
|
-
fields.append(key, value);
|
|
492
|
-
}
|
|
493
|
-
return fields;
|
|
494
|
-
}
|
|
495
|
-
get(name) {
|
|
496
|
-
const tableEntries = this.#table.get(name.toLowerCase());
|
|
497
|
-
if (!tableEntries) {
|
|
498
|
-
return [];
|
|
499
|
-
}
|
|
500
|
-
return tableEntries.map(([, v]) => v);
|
|
501
|
-
}
|
|
502
|
-
/**
|
|
503
|
-
* WIT spec (https://github.com/WebAssembly/WASI/blob/91bb44c3c3a9b1e09187db23c85fa844d1fd6b15/proposals/http/wit/types.wit#L215-L223):
|
|
504
|
-
*
|
|
505
|
-
* > Set all of the values for a name. Clears any existing values for that
|
|
506
|
-
* > name, if they have been set.
|
|
507
|
-
* >
|
|
508
|
-
* > Fails with `header-error.immutable` if the `fields` are immutable.
|
|
509
|
-
* >
|
|
510
|
-
* > Fails with `header-error.invalid-syntax` if the `field-name` or any of
|
|
511
|
-
* > the `field-value`s are syntactically invalid.
|
|
512
|
-
*
|
|
513
|
-
* The existing-branch splice/reuse is an allocation optimization — values are cleared, not retained.
|
|
514
|
-
*/
|
|
515
|
-
set(name, values) {
|
|
516
|
-
if (this.#immutable) {
|
|
517
|
-
throw { tag: "immutable" };
|
|
518
|
-
}
|
|
519
|
-
try {
|
|
520
|
-
validateHeaderName(name);
|
|
521
|
-
} catch {
|
|
522
|
-
throw { tag: "invalid-syntax" };
|
|
523
|
-
}
|
|
524
|
-
for (const value of values) {
|
|
525
|
-
try {
|
|
526
|
-
validateHeaderValue(name, new TextDecoder().decode(value));
|
|
527
|
-
} catch {
|
|
528
|
-
throw { tag: "invalid-syntax" };
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
const lowercased = name.toLowerCase();
|
|
532
|
-
if (_forbiddenHeaders.has(lowercased)) {
|
|
533
|
-
throw { tag: "forbidden" };
|
|
534
|
-
}
|
|
535
|
-
let tableEntries = this.#table.get(lowercased);
|
|
536
|
-
if (tableEntries) {
|
|
537
|
-
this.#entries = this.#entries.filter((entry) => !tableEntries.includes(entry));
|
|
538
|
-
tableEntries.splice(0, tableEntries.length);
|
|
539
|
-
} else {
|
|
540
|
-
this.#table.set(lowercased, []);
|
|
541
|
-
tableEntries = this.#table.get(lowercased);
|
|
542
|
-
}
|
|
543
|
-
for (const value of values) {
|
|
544
|
-
const entry = [name, value];
|
|
545
|
-
this.#entries.push(entry);
|
|
546
|
-
tableEntries.push(entry);
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
has(name) {
|
|
550
|
-
return this.#table.has(name.toLowerCase());
|
|
551
|
-
}
|
|
552
|
-
delete(name) {
|
|
553
|
-
if (this.#immutable) {
|
|
554
|
-
throw { tag: "immutable" };
|
|
555
|
-
}
|
|
556
|
-
const lowercased = name.toLowerCase();
|
|
557
|
-
const tableEntries = this.#table.get(lowercased);
|
|
558
|
-
if (tableEntries) {
|
|
559
|
-
this.#entries = this.#entries.filter((entry) => !tableEntries.includes(entry));
|
|
560
|
-
this.#table.delete(lowercased);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
append(name, value) {
|
|
564
|
-
if (this.#immutable) {
|
|
565
|
-
throw { tag: "immutable" };
|
|
566
|
-
}
|
|
567
|
-
try {
|
|
568
|
-
validateHeaderName(name);
|
|
569
|
-
} catch {
|
|
570
|
-
throw { tag: "invalid-syntax" };
|
|
571
|
-
}
|
|
572
|
-
try {
|
|
573
|
-
validateHeaderValue(name, new TextDecoder().decode(value));
|
|
574
|
-
} catch {
|
|
575
|
-
throw { tag: "invalid-syntax" };
|
|
576
|
-
}
|
|
577
|
-
const lowercased = name.toLowerCase();
|
|
578
|
-
if (_forbiddenHeaders.has(lowercased)) {
|
|
579
|
-
throw { tag: "forbidden" };
|
|
580
|
-
}
|
|
581
|
-
const entry = [name, value];
|
|
582
|
-
this.#entries.push(entry);
|
|
583
|
-
const tableEntries = this.#table.get(lowercased);
|
|
584
|
-
if (tableEntries) {
|
|
585
|
-
tableEntries.push(entry);
|
|
586
|
-
} else {
|
|
587
|
-
this.#table.set(lowercased, [entry]);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
entries() {
|
|
591
|
-
return this.#entries;
|
|
592
|
-
}
|
|
593
|
-
clone() {
|
|
594
|
-
return fieldsFromEntriesChecked(this.#entries);
|
|
595
|
-
}
|
|
596
|
-
static _lock(fields) {
|
|
597
|
-
fields.#immutable = true;
|
|
598
|
-
return fields;
|
|
599
|
-
}
|
|
600
|
-
// assumes entries are already validated
|
|
601
|
-
static _fromEntriesChecked(entries) {
|
|
602
|
-
const fields = new Fields();
|
|
603
|
-
fields.#entries = entries;
|
|
604
|
-
for (const entry of entries) {
|
|
605
|
-
const lowercase = entry[0].toLowerCase();
|
|
606
|
-
const existing = fields.#table.get(lowercase);
|
|
607
|
-
if (existing) {
|
|
608
|
-
existing.push(entry);
|
|
609
|
-
} else {
|
|
610
|
-
fields.#table.set(lowercase, [entry]);
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
return fields;
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
const fieldsLock = Fields._lock;
|
|
617
|
-
delete Fields._lock;
|
|
618
|
-
const fieldsFromEntriesChecked = Fields._fromEntriesChecked;
|
|
619
|
-
delete Fields._fromEntriesChecked;
|
|
620
|
-
|
|
621
|
-
export const outgoingHandler = {
|
|
622
|
-
/**
|
|
623
|
-
* @param {OutgoingRequest} request
|
|
624
|
-
* @param {RequestOptions | undefined} options
|
|
625
|
-
* @returns {FutureIncomingResponse}
|
|
626
|
-
*/
|
|
627
|
-
handle: outgoingRequestHandle,
|
|
628
|
-
};
|
|
629
|
-
|
|
630
|
-
function httpErrorCode(err) {
|
|
631
|
-
if (err.payload) {
|
|
632
|
-
return err.payload;
|
|
633
|
-
}
|
|
634
|
-
return {
|
|
635
|
-
tag: "internal-error",
|
|
636
|
-
val: err.message,
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
export const types = {
|
|
641
|
-
Fields,
|
|
642
|
-
FutureIncomingResponse,
|
|
643
|
-
FutureTrailers,
|
|
644
|
-
IncomingBody,
|
|
645
|
-
IncomingRequest,
|
|
646
|
-
IncomingResponse,
|
|
647
|
-
OutgoingBody,
|
|
648
|
-
OutgoingRequest,
|
|
649
|
-
OutgoingResponse,
|
|
650
|
-
ResponseOutparam,
|
|
651
|
-
RequestOptions,
|
|
652
|
-
httpErrorCode,
|
|
653
|
-
};
|
|
654
|
-
|
|
655
|
-
function schemeString(scheme) {
|
|
656
|
-
if (!scheme) {
|
|
657
|
-
return "https:";
|
|
658
|
-
}
|
|
659
|
-
switch (scheme.tag) {
|
|
660
|
-
case "HTTP":
|
|
661
|
-
return "http:";
|
|
662
|
-
case "HTTPS":
|
|
663
|
-
return "https:";
|
|
664
|
-
case "other":
|
|
665
|
-
return scheme.val.toLowerCase() + ":";
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
const supportedMethods = [
|
|
670
|
-
"get",
|
|
671
|
-
"head",
|
|
672
|
-
"post",
|
|
673
|
-
"put",
|
|
674
|
-
"delete",
|
|
675
|
-
"connect",
|
|
676
|
-
"options",
|
|
677
|
-
"trace",
|
|
678
|
-
"patch",
|
|
679
|
-
];
|
|
680
|
-
function parseMethod(method) {
|
|
681
|
-
const lowercase = method.toLowerCase();
|
|
682
|
-
if (supportedMethods.includes(method.toLowerCase())) {
|
|
683
|
-
return { tag: lowercase };
|
|
684
|
-
}
|
|
685
|
-
return { tag: "other", val: lowercase };
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
const httpServers = new Map();
|
|
689
|
-
let httpServerCnt = 0;
|
|
690
|
-
export class HTTPServer {
|
|
691
|
-
#id = ++httpServerCnt;
|
|
692
|
-
#stopped = false;
|
|
693
|
-
#liveEventLoopInterval;
|
|
694
|
-
constructor(incomingHandler) {
|
|
695
|
-
httpServers.set(this.#id, this);
|
|
696
|
-
if (typeof incomingHandler?.handle !== "function") {
|
|
697
|
-
console.error("Not a valid HTTP server component to execute.");
|
|
698
|
-
process.exit(1);
|
|
699
|
-
}
|
|
700
|
-
registerIncomingHttpHandler(
|
|
701
|
-
this.#id,
|
|
702
|
-
({ method, pathWithQuery, host, headers, responseId, streamId }) => {
|
|
703
|
-
const textEncoder = new TextEncoder();
|
|
704
|
-
const request = incomingRequestCreate(
|
|
705
|
-
parseMethod(method),
|
|
706
|
-
pathWithQuery,
|
|
707
|
-
{ tag: "HTTP" },
|
|
708
|
-
host,
|
|
709
|
-
fieldsLock(
|
|
710
|
-
fieldsFromEntriesChecked(
|
|
711
|
-
headers
|
|
712
|
-
.filter(([key]) => !_forbiddenHeaders.has(key))
|
|
713
|
-
.map(([key, val]) => [key, textEncoder.encode(val)]),
|
|
714
|
-
),
|
|
715
|
-
),
|
|
716
|
-
streamId,
|
|
717
|
-
);
|
|
718
|
-
let outgoingBodyStreamId;
|
|
719
|
-
const responseOutparam = responseOutparamCreate((response) => {
|
|
720
|
-
if (response.tag === "ok") {
|
|
721
|
-
const outgoingResponse = response.val;
|
|
722
|
-
const statusCode = outgoingResponse.statusCode();
|
|
723
|
-
const headers = outgoingResponse.headers().entries();
|
|
724
|
-
const body = outgoingResponseBody(outgoingResponse);
|
|
725
|
-
outgoingBodyStreamId = outgoingBodyOutputStreamId(body);
|
|
726
|
-
ioCall(HTTP_SERVER_SET_OUTGOING_RESPONSE, responseId, {
|
|
727
|
-
statusCode,
|
|
728
|
-
headers,
|
|
729
|
-
streamId: outgoingBodyStreamId,
|
|
730
|
-
});
|
|
731
|
-
} else {
|
|
732
|
-
ioCall(HTTP_SERVER_CLEAR_OUTGOING_RESPONSE, responseId, null);
|
|
733
|
-
console.error(response.val);
|
|
734
|
-
process.exit(1);
|
|
735
|
-
}
|
|
736
|
-
});
|
|
737
|
-
incomingHandler.handle(request, responseOutparam);
|
|
738
|
-
if (outgoingBodyStreamId) {
|
|
739
|
-
ioCall(OUTPUT_STREAM_DISPOSE, outgoingBodyStreamId, null);
|
|
740
|
-
}
|
|
741
|
-
},
|
|
742
|
-
);
|
|
743
|
-
}
|
|
744
|
-
listen(port, host) {
|
|
745
|
-
// set a dummy interval, to keep the process alive since the server is off-thread
|
|
746
|
-
this.#liveEventLoopInterval = setInterval(() => {}, 10_000);
|
|
747
|
-
ioCall(HTTP_SERVER_START, this.#id, { port, host });
|
|
748
|
-
}
|
|
749
|
-
address() {
|
|
750
|
-
return ioCall(HTTP_SERVER_GET_ADDRESS, this.#id);
|
|
751
|
-
}
|
|
752
|
-
stop() {
|
|
753
|
-
if (this.#stopped) {
|
|
754
|
-
return;
|
|
755
|
-
}
|
|
756
|
-
clearInterval(this.#liveEventLoopInterval);
|
|
757
|
-
ioCall(HTTP_SERVER_STOP, this.#id, null);
|
|
758
|
-
httpServers.delete(this.#id);
|
|
759
|
-
this.#stopped = true;
|
|
760
|
-
}
|
|
761
|
-
close() {
|
|
762
|
-
this.stop();
|
|
763
|
-
}
|
|
764
|
-
}
|