wasm-platform-kit 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +224 -2
- package/assets/wasi/LICENSE +220 -0
- package/assets/wasi/cli.d.ts +43 -0
- package/assets/wasi/cli.js +147 -0
- package/assets/wasi/clocks.d.ts +3 -0
- package/assets/wasi/clocks.js +55 -0
- package/assets/wasi/common.d.ts +5 -0
- package/assets/wasi/common.js +16 -0
- package/assets/wasi/config.d.ts +2 -0
- package/assets/wasi/config.js +7 -0
- package/assets/wasi/environment.d.ts +5 -0
- package/assets/wasi/environment.js +24 -0
- package/assets/wasi/filesystem.d.ts +117 -0
- package/assets/wasi/filesystem.js +341 -0
- package/assets/wasi/helpers/cli.d.ts +7 -0
- package/assets/wasi/helpers/cli.js +25 -0
- package/assets/wasi/helpers/filesystem.d.ts +6 -0
- package/assets/wasi/helpers/filesystem.js +10 -0
- package/assets/wasi/http.d.ts +13 -0
- package/assets/wasi/http.js +953 -0
- package/assets/wasi/in-memory-filesystem.d.ts +38 -0
- package/assets/wasi/in-memory-filesystem.js +620 -0
- package/assets/wasi/in-memory-http.d.ts +8 -0
- package/assets/wasi/in-memory-http.js +11 -0
- package/assets/wasi/in-memory-sockets.d.ts +34 -0
- package/assets/wasi/in-memory-sockets.js +294 -0
- package/assets/wasi/index.d.ts +7 -0
- package/assets/wasi/index.js +7 -0
- package/assets/wasi/io.d.ts +67 -0
- package/assets/wasi/io.js +366 -0
- package/assets/wasi/opfs-filesystem.d.ts +52 -0
- package/assets/wasi/opfs-filesystem.js +262 -0
- package/assets/wasi/random.d.ts +4 -0
- package/assets/wasi/random.js +51 -0
- package/assets/wasi/sockets.d.ts +9 -0
- package/assets/wasi/sockets.js +85 -0
- package/browser.mjs +209 -0
- package/client.mjs +138 -0
- package/index.d.ts +199 -0
- package/install.d.ts +22 -0
- package/install.mjs +176 -0
- package/native-registration.mjs +70 -0
- package/node.mjs +332 -0
- package/package.json +66 -4
- package/policy.mjs +433 -0
- package/worker.mjs +389 -0
|
@@ -0,0 +1,953 @@
|
|
|
1
|
+
import { inputStreamCreate, ioErrorCreate, outputStreamCreate, pollableCreate } from "./io.js";
|
|
2
|
+
export { InMemoryHttpClient } from "./in-memory-http.js";
|
|
3
|
+
const symbolDispose = Symbol.dispose || Symbol.for("dispose");
|
|
4
|
+
const utf8Encoder = new TextEncoder();
|
|
5
|
+
const utf8Decoder = new TextDecoder();
|
|
6
|
+
const forbiddenHeaders = new Set(["connection", "keep-alive", "host"]);
|
|
7
|
+
const DEFAULT_HTTP_TIMEOUT_NS = 600000000000n;
|
|
8
|
+
// RFC 9110 compliant header validation
|
|
9
|
+
const TOKEN_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
10
|
+
const FIELD_VALUE_RE = /^[\t\x20-\x7E\x80-\xFF]*$/;
|
|
11
|
+
const BRACKETED_IPV6_AUTHORITY_RE = /^\[([0-9A-Fa-f:.]+)\](?::([0-9]+))?$/;
|
|
12
|
+
const DNS_OR_IPV4_AUTHORITY_RE = /^([a-zA-Z0-9.-]+)(?::([0-9]+))?$/;
|
|
13
|
+
function validateHeaderName(name) {
|
|
14
|
+
if (!TOKEN_RE.test(name)) {
|
|
15
|
+
throw { tag: "invalid-syntax" };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function validateHeaderValue(value) {
|
|
19
|
+
const str = typeof value === "string" ? value : utf8Decoder.decode(value);
|
|
20
|
+
if (!FIELD_VALUE_RE.test(str)) {
|
|
21
|
+
throw { tag: "invalid-syntax" };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
class Fields {
|
|
25
|
+
#immutable = false;
|
|
26
|
+
#entries = [];
|
|
27
|
+
#table = new Map();
|
|
28
|
+
static fromList(entries) {
|
|
29
|
+
const fields = new Fields();
|
|
30
|
+
for (const [key, value] of entries) {
|
|
31
|
+
fields.append(key, value);
|
|
32
|
+
}
|
|
33
|
+
return fields;
|
|
34
|
+
}
|
|
35
|
+
get(name) {
|
|
36
|
+
const tableEntries = this.#table.get(name.toLowerCase());
|
|
37
|
+
if (!tableEntries) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
return tableEntries.map(([, v]) => v);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* WIT spec (https://github.com/WebAssembly/WASI/blob/91bb44c3c3a9b1e09187db23c85fa844d1fd6b15/proposals/http/wit/types.wit#L215-L223):
|
|
44
|
+
*
|
|
45
|
+
* > Set all of the values for a name. Clears any existing values for that
|
|
46
|
+
* > name, if they have been set.
|
|
47
|
+
* >
|
|
48
|
+
* > Fails with `header-error.immutable` if the `fields` are immutable.
|
|
49
|
+
* >
|
|
50
|
+
* > Fails with `header-error.invalid-syntax` if the `field-name` or any of
|
|
51
|
+
* > the `field-value`s are syntactically invalid.
|
|
52
|
+
*
|
|
53
|
+
* The existing-branch splice/reuse is an allocation optimization — values are cleared, not retained.
|
|
54
|
+
*/
|
|
55
|
+
set(name, values) {
|
|
56
|
+
if (this.#immutable) {
|
|
57
|
+
throw { tag: "immutable" };
|
|
58
|
+
}
|
|
59
|
+
validateHeaderName(name);
|
|
60
|
+
for (const value of values) {
|
|
61
|
+
validateHeaderValue(value);
|
|
62
|
+
}
|
|
63
|
+
const lowercased = name.toLowerCase();
|
|
64
|
+
if (forbiddenHeaders.has(lowercased)) {
|
|
65
|
+
throw { tag: "forbidden" };
|
|
66
|
+
}
|
|
67
|
+
const tableEntries = this.#table.get(lowercased);
|
|
68
|
+
if (tableEntries) {
|
|
69
|
+
this.#entries = this.#entries.filter((entry) => !tableEntries.includes(entry));
|
|
70
|
+
tableEntries.splice(0, tableEntries.length);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
this.#table.set(lowercased, []);
|
|
74
|
+
}
|
|
75
|
+
const newTableEntries = this.#table.get(lowercased);
|
|
76
|
+
for (const value of values) {
|
|
77
|
+
const entry = [name, value];
|
|
78
|
+
this.#entries.push(entry);
|
|
79
|
+
newTableEntries.push(entry);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
has(name) {
|
|
83
|
+
return this.#table.has(name.toLowerCase());
|
|
84
|
+
}
|
|
85
|
+
delete(name) {
|
|
86
|
+
if (this.#immutable) {
|
|
87
|
+
throw { tag: "immutable" };
|
|
88
|
+
}
|
|
89
|
+
const lowercased = name.toLowerCase();
|
|
90
|
+
const tableEntries = this.#table.get(lowercased);
|
|
91
|
+
if (tableEntries) {
|
|
92
|
+
this.#entries = this.#entries.filter((entry) => !tableEntries.includes(entry));
|
|
93
|
+
this.#table.delete(lowercased);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
append(name, value) {
|
|
97
|
+
if (this.#immutable) {
|
|
98
|
+
throw { tag: "immutable" };
|
|
99
|
+
}
|
|
100
|
+
validateHeaderName(name);
|
|
101
|
+
validateHeaderValue(value);
|
|
102
|
+
const lowercased = name.toLowerCase();
|
|
103
|
+
if (forbiddenHeaders.has(lowercased)) {
|
|
104
|
+
throw { tag: "forbidden" };
|
|
105
|
+
}
|
|
106
|
+
const entry = [name, value];
|
|
107
|
+
this.#entries.push(entry);
|
|
108
|
+
const tableEntries = this.#table.get(lowercased);
|
|
109
|
+
if (tableEntries) {
|
|
110
|
+
tableEntries.push(entry);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
this.#table.set(lowercased, [entry]);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
entries() {
|
|
117
|
+
return this.#entries;
|
|
118
|
+
}
|
|
119
|
+
clone() {
|
|
120
|
+
return fieldsFromEntriesChecked(this.#entries);
|
|
121
|
+
}
|
|
122
|
+
static _lock(fields) {
|
|
123
|
+
fields.#immutable = true;
|
|
124
|
+
return fields;
|
|
125
|
+
}
|
|
126
|
+
static _fromEntriesChecked(entries) {
|
|
127
|
+
const fields = new Fields();
|
|
128
|
+
fields.#entries = entries;
|
|
129
|
+
for (const entry of entries) {
|
|
130
|
+
const lowercase = entry[0].toLowerCase();
|
|
131
|
+
const existing = fields.#table.get(lowercase);
|
|
132
|
+
if (existing) {
|
|
133
|
+
existing.push(entry);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
fields.#table.set(lowercase, [entry]);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return fields;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const fieldsLock = Fields._lock;
|
|
143
|
+
// @ts-expect-error - Deleting static method
|
|
144
|
+
delete Fields._lock;
|
|
145
|
+
const fieldsFromEntriesChecked = Fields._fromEntriesChecked;
|
|
146
|
+
// @ts-expect-error - Deleting static method
|
|
147
|
+
delete Fields._fromEntriesChecked;
|
|
148
|
+
class RequestOptions {
|
|
149
|
+
#connectTimeout;
|
|
150
|
+
#firstByteTimeout;
|
|
151
|
+
#betweenBytesTimeout;
|
|
152
|
+
connectTimeout() {
|
|
153
|
+
return this.#connectTimeout;
|
|
154
|
+
}
|
|
155
|
+
setConnectTimeout(duration) {
|
|
156
|
+
if (duration !== undefined && duration < 0n) {
|
|
157
|
+
throw new Error("duration must not be negative");
|
|
158
|
+
}
|
|
159
|
+
this.#connectTimeout = duration;
|
|
160
|
+
}
|
|
161
|
+
firstByteTimeout() {
|
|
162
|
+
return this.#firstByteTimeout;
|
|
163
|
+
}
|
|
164
|
+
setFirstByteTimeout(duration) {
|
|
165
|
+
if (duration !== undefined && duration < 0n) {
|
|
166
|
+
throw new Error("duration must not be negative");
|
|
167
|
+
}
|
|
168
|
+
this.#firstByteTimeout = duration;
|
|
169
|
+
}
|
|
170
|
+
betweenBytesTimeout() {
|
|
171
|
+
return this.#betweenBytesTimeout;
|
|
172
|
+
}
|
|
173
|
+
setBetweenBytesTimeout(duration) {
|
|
174
|
+
if (duration !== undefined && duration < 0n) {
|
|
175
|
+
throw new Error("duration must not be negative");
|
|
176
|
+
}
|
|
177
|
+
this.#betweenBytesTimeout = duration;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
class OutgoingBody {
|
|
181
|
+
#outputStream = null;
|
|
182
|
+
#chunks = [];
|
|
183
|
+
#finished = false;
|
|
184
|
+
#resolveFinished;
|
|
185
|
+
#finishedPromise = new Promise((resolve) => (this.#resolveFinished = resolve));
|
|
186
|
+
#requestStream = null;
|
|
187
|
+
#requestStreamController = null;
|
|
188
|
+
#requestStreamCancelled = false;
|
|
189
|
+
write() {
|
|
190
|
+
const outputStream = this.#outputStream;
|
|
191
|
+
if (outputStream === null) {
|
|
192
|
+
throw undefined;
|
|
193
|
+
}
|
|
194
|
+
this.#outputStream = null;
|
|
195
|
+
return outputStream;
|
|
196
|
+
}
|
|
197
|
+
static finish(body, trailers) {
|
|
198
|
+
if (trailers) {
|
|
199
|
+
throw { tag: "internal-error", val: "trailers unsupported" };
|
|
200
|
+
}
|
|
201
|
+
if (body.#finished) {
|
|
202
|
+
throw { tag: "internal-error", val: "body already finished" };
|
|
203
|
+
}
|
|
204
|
+
body.#finished = true;
|
|
205
|
+
if (!body.#requestStreamCancelled) {
|
|
206
|
+
body.#requestStreamController?.close();
|
|
207
|
+
}
|
|
208
|
+
body.#resolveFinished();
|
|
209
|
+
}
|
|
210
|
+
#bodyData() {
|
|
211
|
+
if (this.#chunks.length === 0) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
let totalLen = 0;
|
|
215
|
+
for (const chunk of this.#chunks) {
|
|
216
|
+
totalLen += chunk.byteLength;
|
|
217
|
+
}
|
|
218
|
+
const result = new Uint8Array(totalLen);
|
|
219
|
+
let offset = 0;
|
|
220
|
+
for (const chunk of this.#chunks) {
|
|
221
|
+
result.set(chunk, offset);
|
|
222
|
+
offset += chunk.byteLength;
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
static async _finishedBodyData(outgoingBody) {
|
|
227
|
+
await outgoingBody.#finishedPromise;
|
|
228
|
+
return outgoingBody.#bodyData();
|
|
229
|
+
}
|
|
230
|
+
static _requestBodyStream(outgoingBody) {
|
|
231
|
+
if (outgoingBody.#requestStream === null) {
|
|
232
|
+
outgoingBody.#requestStream = new ReadableStream({
|
|
233
|
+
start(controller) {
|
|
234
|
+
outgoingBody.#requestStreamController = controller;
|
|
235
|
+
for (const chunk of outgoingBody.#chunks) {
|
|
236
|
+
controller.enqueue(chunk);
|
|
237
|
+
}
|
|
238
|
+
outgoingBody.#chunks.length = 0;
|
|
239
|
+
if (outgoingBody.#finished) {
|
|
240
|
+
controller.close();
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
cancel() {
|
|
244
|
+
outgoingBody.#requestStreamCancelled = true;
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return outgoingBody.#requestStream;
|
|
249
|
+
}
|
|
250
|
+
static _create() {
|
|
251
|
+
const outgoingBody = new OutgoingBody();
|
|
252
|
+
const chunks = outgoingBody.#chunks;
|
|
253
|
+
outgoingBody.#outputStream = outputStreamCreate({
|
|
254
|
+
write(buf) {
|
|
255
|
+
if (outgoingBody.#finished || outgoingBody.#requestStreamCancelled) {
|
|
256
|
+
throw { tag: "closed" };
|
|
257
|
+
}
|
|
258
|
+
const chunk = new Uint8Array(buf);
|
|
259
|
+
if (outgoingBody.#requestStreamController) {
|
|
260
|
+
outgoingBody.#requestStreamController.enqueue(chunk);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
chunks.push(chunk);
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
blockingFlush() { },
|
|
267
|
+
subscribe() {
|
|
268
|
+
return pollableCreate();
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
return outgoingBody;
|
|
272
|
+
}
|
|
273
|
+
[symbolDispose]() { }
|
|
274
|
+
}
|
|
275
|
+
const outgoingBodyCreate = OutgoingBody._create;
|
|
276
|
+
// @ts-expect-error - Deleting static method
|
|
277
|
+
delete OutgoingBody._create;
|
|
278
|
+
const outgoingBodyFinishedData = OutgoingBody._finishedBodyData;
|
|
279
|
+
// @ts-expect-error - Deleting static method
|
|
280
|
+
delete OutgoingBody._finishedBodyData;
|
|
281
|
+
const outgoingBodyRequestStream = OutgoingBody._requestBodyStream;
|
|
282
|
+
// @ts-expect-error - Deleting static method
|
|
283
|
+
delete OutgoingBody._requestBodyStream;
|
|
284
|
+
class OutgoingRequest {
|
|
285
|
+
#method = { tag: "get" };
|
|
286
|
+
#scheme = undefined;
|
|
287
|
+
#pathWithQuery = undefined;
|
|
288
|
+
#authority = undefined;
|
|
289
|
+
#headers;
|
|
290
|
+
#body;
|
|
291
|
+
#bodyRequested = false;
|
|
292
|
+
constructor(headers) {
|
|
293
|
+
fieldsLock(headers);
|
|
294
|
+
this.#headers = headers;
|
|
295
|
+
this.#body = outgoingBodyCreate();
|
|
296
|
+
}
|
|
297
|
+
body() {
|
|
298
|
+
if (this.#bodyRequested) {
|
|
299
|
+
throw new Error("Body already requested");
|
|
300
|
+
}
|
|
301
|
+
this.#bodyRequested = true;
|
|
302
|
+
return this.#body;
|
|
303
|
+
}
|
|
304
|
+
method() {
|
|
305
|
+
return this.#method;
|
|
306
|
+
}
|
|
307
|
+
setMethod(method) {
|
|
308
|
+
if (method.tag === "other" && method.val && !method.val.match(/^[a-zA-Z-]+$/)) {
|
|
309
|
+
throw undefined;
|
|
310
|
+
}
|
|
311
|
+
this.#method = method;
|
|
312
|
+
}
|
|
313
|
+
pathWithQuery() {
|
|
314
|
+
return this.#pathWithQuery;
|
|
315
|
+
}
|
|
316
|
+
setPathWithQuery(pathWithQuery) {
|
|
317
|
+
if (pathWithQuery && !pathWithQuery.match(/^[/?][a-zA-Z0-9.\-_~!$&'()*+,;=:@%?/]*$/)) {
|
|
318
|
+
throw undefined;
|
|
319
|
+
}
|
|
320
|
+
this.#pathWithQuery = pathWithQuery;
|
|
321
|
+
}
|
|
322
|
+
scheme() {
|
|
323
|
+
return this.#scheme;
|
|
324
|
+
}
|
|
325
|
+
setScheme(scheme) {
|
|
326
|
+
if (scheme?.tag === "other" && scheme.val && !scheme.val.match(/^[a-zA-Z]+$/)) {
|
|
327
|
+
throw undefined;
|
|
328
|
+
}
|
|
329
|
+
this.#scheme = scheme;
|
|
330
|
+
}
|
|
331
|
+
authority() {
|
|
332
|
+
return this.#authority;
|
|
333
|
+
}
|
|
334
|
+
setAuthority(authority) {
|
|
335
|
+
if (authority !== undefined) {
|
|
336
|
+
const match = authority.startsWith("[")
|
|
337
|
+
? authority.match(BRACKETED_IPV6_AUTHORITY_RE)
|
|
338
|
+
: authority.match(DNS_OR_IPV4_AUTHORITY_RE);
|
|
339
|
+
if (!match || (match[2] !== undefined && Number(match[2]) > 65535)) {
|
|
340
|
+
throw undefined;
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
const parsed = new URL(`http://${authority}/`);
|
|
344
|
+
if (parsed.username || parsed.password || !parsed.hostname) {
|
|
345
|
+
throw undefined;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
throw undefined;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
this.#authority = authority;
|
|
353
|
+
}
|
|
354
|
+
headers() {
|
|
355
|
+
return this.#headers;
|
|
356
|
+
}
|
|
357
|
+
[symbolDispose]() { }
|
|
358
|
+
static _handle(request, options, config = {}) {
|
|
359
|
+
const scheme = schemeString(request.#scheme);
|
|
360
|
+
const method = "val" in request.#method ? request.#method.val : request.#method.tag;
|
|
361
|
+
if (!request.#pathWithQuery) {
|
|
362
|
+
throw { tag: "HTTP-request-URI-invalid" };
|
|
363
|
+
}
|
|
364
|
+
const url = `${scheme}//${request.#authority || ""}${request.#pathWithQuery}`;
|
|
365
|
+
const headers = new Headers();
|
|
366
|
+
for (const [key, value] of request.#headers.entries()) {
|
|
367
|
+
const lowerKey = key.toLowerCase();
|
|
368
|
+
if (!forbiddenHeaders.has(lowerKey)) {
|
|
369
|
+
headers.append(key, utf8Decoder.decode(value));
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
// Request streams are opt-in because Firefox and Safari do not yet support them.
|
|
373
|
+
// The portable default buffers until the guest explicitly finishes the body.
|
|
374
|
+
const bodyData = request.#bodyRequested
|
|
375
|
+
? config.streamingRequestBodies
|
|
376
|
+
? outgoingBodyRequestStream(request.#body)
|
|
377
|
+
: outgoingBodyFinishedData(request.#body)
|
|
378
|
+
: null;
|
|
379
|
+
let timeoutMs = Number(DEFAULT_HTTP_TIMEOUT_NS / 1000000n);
|
|
380
|
+
if (options) {
|
|
381
|
+
const ct = options.connectTimeout?.() ?? DEFAULT_HTTP_TIMEOUT_NS;
|
|
382
|
+
const fbt = options.firstByteTimeout?.() ?? DEFAULT_HTTP_TIMEOUT_NS;
|
|
383
|
+
const minTimeout = ct < fbt ? ct : fbt;
|
|
384
|
+
timeoutMs = Number(minTimeout / 1000000n);
|
|
385
|
+
}
|
|
386
|
+
return futureIncomingResponseCreate(url, method.toUpperCase(), headers, bodyData, timeoutMs);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const outgoingRequestHandle = OutgoingRequest._handle;
|
|
390
|
+
// @ts-expect-error - Deleting static method
|
|
391
|
+
delete OutgoingRequest._handle;
|
|
392
|
+
class IncomingBody {
|
|
393
|
+
#finished = false;
|
|
394
|
+
#stream = null;
|
|
395
|
+
stream() {
|
|
396
|
+
if (!this.#stream) {
|
|
397
|
+
throw undefined;
|
|
398
|
+
}
|
|
399
|
+
const stream = this.#stream;
|
|
400
|
+
this.#stream = null;
|
|
401
|
+
return stream;
|
|
402
|
+
}
|
|
403
|
+
static finish(incomingBody) {
|
|
404
|
+
if (incomingBody.#finished) {
|
|
405
|
+
throw new Error("incoming body already finished");
|
|
406
|
+
}
|
|
407
|
+
incomingBody.#finished = true;
|
|
408
|
+
return futureTrailersCreate();
|
|
409
|
+
}
|
|
410
|
+
[symbolDispose]() { }
|
|
411
|
+
static _create(fetchResponse, bufferedBody) {
|
|
412
|
+
const incomingBody = new IncomingBody();
|
|
413
|
+
let buffer = bufferedBody ?? null;
|
|
414
|
+
let bufferOffset = 0;
|
|
415
|
+
let done = bufferedBody !== undefined;
|
|
416
|
+
let reader = null;
|
|
417
|
+
let readPromise = null;
|
|
418
|
+
let readError = null;
|
|
419
|
+
let disposed = false;
|
|
420
|
+
function ready() {
|
|
421
|
+
return done || (buffer !== null && bufferOffset < buffer.byteLength);
|
|
422
|
+
}
|
|
423
|
+
function startRead() {
|
|
424
|
+
if (readPromise || ready()) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if (!fetchResponse.body) {
|
|
428
|
+
done = true;
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
reader ??= fetchResponse.body.getReader();
|
|
432
|
+
const activeReader = reader;
|
|
433
|
+
readPromise = (async () => {
|
|
434
|
+
try {
|
|
435
|
+
// Empty Fetch chunks do not make a WASI input stream readable.
|
|
436
|
+
// Keep a single read in flight and buffer at most one nonempty chunk.
|
|
437
|
+
while (!done) {
|
|
438
|
+
const result = await activeReader.read();
|
|
439
|
+
if (disposed) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
if (result.done) {
|
|
443
|
+
done = true;
|
|
444
|
+
}
|
|
445
|
+
else if (result.value.byteLength > 0) {
|
|
446
|
+
buffer = result.value;
|
|
447
|
+
bufferOffset = 0;
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
catch (cause) {
|
|
453
|
+
done = true;
|
|
454
|
+
if (!disposed) {
|
|
455
|
+
readError = ioErrorCreate(cause instanceof Error ? cause.message : String(cause));
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
finally {
|
|
459
|
+
readPromise = null;
|
|
460
|
+
if (done) {
|
|
461
|
+
activeReader.releaseLock();
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
})();
|
|
465
|
+
}
|
|
466
|
+
async function waitForReadable() {
|
|
467
|
+
while (!ready()) {
|
|
468
|
+
startRead();
|
|
469
|
+
await readPromise;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
function read(len) {
|
|
473
|
+
if (readError) {
|
|
474
|
+
const error = readError;
|
|
475
|
+
readError = null;
|
|
476
|
+
throw { tag: "last-operation-failed", val: error };
|
|
477
|
+
}
|
|
478
|
+
if (done && (buffer === null || bufferOffset >= buffer.byteLength)) {
|
|
479
|
+
throw { tag: "closed" };
|
|
480
|
+
}
|
|
481
|
+
if (buffer === null || bufferOffset >= buffer.byteLength) {
|
|
482
|
+
startRead();
|
|
483
|
+
return new Uint8Array(0);
|
|
484
|
+
}
|
|
485
|
+
const toRead = Math.min(Number(len), buffer.byteLength - bufferOffset);
|
|
486
|
+
const slice = buffer.slice(bufferOffset, bufferOffset + toRead);
|
|
487
|
+
bufferOffset += toRead;
|
|
488
|
+
if (bufferOffset >= buffer.byteLength) {
|
|
489
|
+
buffer = null;
|
|
490
|
+
bufferOffset = 0;
|
|
491
|
+
startRead();
|
|
492
|
+
}
|
|
493
|
+
return slice;
|
|
494
|
+
}
|
|
495
|
+
function blockingRead(len) {
|
|
496
|
+
if (len === 0n || ready()) {
|
|
497
|
+
return read(len);
|
|
498
|
+
}
|
|
499
|
+
return waitForReadable().then(() => read(len));
|
|
500
|
+
}
|
|
501
|
+
function blockingSkip(len) {
|
|
502
|
+
const result = blockingRead(len);
|
|
503
|
+
return result instanceof Promise
|
|
504
|
+
? result.then((bytes) => BigInt(bytes.byteLength))
|
|
505
|
+
: BigInt(result.byteLength);
|
|
506
|
+
}
|
|
507
|
+
incomingBody.#stream = inputStreamCreate({
|
|
508
|
+
read,
|
|
509
|
+
// WIT declares synchronous signatures; JSPI awaits these host results
|
|
510
|
+
// for blocking imports. Preserve synchronous results for buffered bodies.
|
|
511
|
+
blockingRead: blockingRead,
|
|
512
|
+
blockingSkip: blockingSkip,
|
|
513
|
+
subscribe() {
|
|
514
|
+
return pollableCreate({ ready, wait: waitForReadable });
|
|
515
|
+
},
|
|
516
|
+
drop() {
|
|
517
|
+
disposed = true;
|
|
518
|
+
done = true;
|
|
519
|
+
buffer = null;
|
|
520
|
+
readError = null;
|
|
521
|
+
if (reader) {
|
|
522
|
+
const activeReader = reader;
|
|
523
|
+
void activeReader
|
|
524
|
+
.cancel()
|
|
525
|
+
.catch(() => { })
|
|
526
|
+
.finally(() => {
|
|
527
|
+
activeReader.releaseLock();
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
});
|
|
532
|
+
if (bufferedBody === undefined) {
|
|
533
|
+
startRead();
|
|
534
|
+
}
|
|
535
|
+
return incomingBody;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
const incomingBodyCreate = IncomingBody._create;
|
|
539
|
+
// @ts-expect-error - Deleting static method
|
|
540
|
+
delete IncomingBody._create;
|
|
541
|
+
class IncomingResponse {
|
|
542
|
+
#headers;
|
|
543
|
+
#status = 0;
|
|
544
|
+
#body;
|
|
545
|
+
status() {
|
|
546
|
+
return this.#status;
|
|
547
|
+
}
|
|
548
|
+
headers() {
|
|
549
|
+
return this.#headers;
|
|
550
|
+
}
|
|
551
|
+
consume() {
|
|
552
|
+
if (this.#body === undefined) {
|
|
553
|
+
throw undefined;
|
|
554
|
+
}
|
|
555
|
+
const body = this.#body;
|
|
556
|
+
this.#body = undefined;
|
|
557
|
+
return body;
|
|
558
|
+
}
|
|
559
|
+
[symbolDispose]() { }
|
|
560
|
+
static _create(fetchResponse) {
|
|
561
|
+
const res = new IncomingResponse();
|
|
562
|
+
res.#status = fetchResponse.status;
|
|
563
|
+
const headerEntries = [];
|
|
564
|
+
const encoder = new TextEncoder();
|
|
565
|
+
fetchResponse.headers.forEach((value, key) => {
|
|
566
|
+
headerEntries.push([key, encoder.encode(value)]);
|
|
567
|
+
});
|
|
568
|
+
res.#headers = fieldsLock(fieldsFromEntriesChecked(headerEntries));
|
|
569
|
+
res.#body = incomingBodyCreate(fetchResponse);
|
|
570
|
+
return res;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
const incomingResponseCreate = IncomingResponse._create;
|
|
574
|
+
// @ts-expect-error - Deleting static method
|
|
575
|
+
delete IncomingResponse._create;
|
|
576
|
+
class IncomingRequest {
|
|
577
|
+
#request;
|
|
578
|
+
#headers;
|
|
579
|
+
#body;
|
|
580
|
+
method() {
|
|
581
|
+
const method = this.#request.method.toLowerCase();
|
|
582
|
+
return { tag: method };
|
|
583
|
+
}
|
|
584
|
+
pathWithQuery() {
|
|
585
|
+
const url = new URL(this.#request.url);
|
|
586
|
+
return `${url.pathname}${url.search}`;
|
|
587
|
+
}
|
|
588
|
+
scheme() {
|
|
589
|
+
const protocol = new URL(this.#request.url).protocol;
|
|
590
|
+
if (protocol === "http:") {
|
|
591
|
+
return { tag: "HTTP" };
|
|
592
|
+
}
|
|
593
|
+
if (protocol === "https:") {
|
|
594
|
+
return { tag: "HTTPS" };
|
|
595
|
+
}
|
|
596
|
+
return { tag: "other", val: protocol.slice(0, -1) };
|
|
597
|
+
}
|
|
598
|
+
authority() {
|
|
599
|
+
return new URL(this.#request.url).host;
|
|
600
|
+
}
|
|
601
|
+
headers() {
|
|
602
|
+
return this.#headers;
|
|
603
|
+
}
|
|
604
|
+
consume() {
|
|
605
|
+
if (!this.#body) {
|
|
606
|
+
throw new Error("incoming request body already consumed");
|
|
607
|
+
}
|
|
608
|
+
const body = this.#body;
|
|
609
|
+
this.#body = undefined;
|
|
610
|
+
return body;
|
|
611
|
+
}
|
|
612
|
+
static _create(request, bufferedBody) {
|
|
613
|
+
const incoming = new IncomingRequest();
|
|
614
|
+
incoming.#request = request.clone();
|
|
615
|
+
const encoder = new TextEncoder();
|
|
616
|
+
incoming.#headers = fieldsLock(fieldsFromEntriesChecked([...request.headers.entries()].map(([name, value]) => [
|
|
617
|
+
name,
|
|
618
|
+
encoder.encode(value),
|
|
619
|
+
])));
|
|
620
|
+
incoming.#body = incomingBodyCreate(new Response(request.body), bufferedBody);
|
|
621
|
+
return incoming;
|
|
622
|
+
}
|
|
623
|
+
static _toRequest(request) {
|
|
624
|
+
return request.#request;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
const incomingRequestCreate = IncomingRequest._create;
|
|
628
|
+
// @ts-expect-error - Deleting static method
|
|
629
|
+
delete IncomingRequest._create;
|
|
630
|
+
const incomingRequestToRequest = IncomingRequest._toRequest;
|
|
631
|
+
// @ts-expect-error - Deleting static method
|
|
632
|
+
delete IncomingRequest._toRequest;
|
|
633
|
+
class OutgoingResponse {
|
|
634
|
+
#headers;
|
|
635
|
+
#status = 200;
|
|
636
|
+
#body = outgoingBodyCreate();
|
|
637
|
+
#bodyRequested = false;
|
|
638
|
+
constructor(headers) {
|
|
639
|
+
fieldsLock(headers);
|
|
640
|
+
this.#headers = headers;
|
|
641
|
+
}
|
|
642
|
+
statusCode() {
|
|
643
|
+
return this.#status;
|
|
644
|
+
}
|
|
645
|
+
setStatusCode(statusCode) {
|
|
646
|
+
if (!Number.isInteger(statusCode) || statusCode < 100 || statusCode > 999) {
|
|
647
|
+
throw new TypeError("invalid HTTP status code");
|
|
648
|
+
}
|
|
649
|
+
this.#status = statusCode;
|
|
650
|
+
}
|
|
651
|
+
headers() {
|
|
652
|
+
return this.#headers;
|
|
653
|
+
}
|
|
654
|
+
body() {
|
|
655
|
+
if (this.#bodyRequested) {
|
|
656
|
+
throw new Error("outgoing response body already requested");
|
|
657
|
+
}
|
|
658
|
+
this.#bodyRequested = true;
|
|
659
|
+
return this.#body;
|
|
660
|
+
}
|
|
661
|
+
static async _toResponse(response) {
|
|
662
|
+
const headers = new Headers();
|
|
663
|
+
for (const [name, value] of response.#headers.entries()) {
|
|
664
|
+
headers.append(name, utf8Decoder.decode(value));
|
|
665
|
+
}
|
|
666
|
+
const body = response.#bodyRequested
|
|
667
|
+
? await outgoingBodyFinishedData(response.#body)
|
|
668
|
+
: null;
|
|
669
|
+
return new Response(body, {
|
|
670
|
+
status: response.#status,
|
|
671
|
+
headers,
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const outgoingResponseToResponse = OutgoingResponse._toResponse;
|
|
676
|
+
// @ts-expect-error - Deleting static method
|
|
677
|
+
delete OutgoingResponse._toResponse;
|
|
678
|
+
class ResponseOutparam {
|
|
679
|
+
#used = false;
|
|
680
|
+
#resolve;
|
|
681
|
+
#reject;
|
|
682
|
+
static set(param, response) {
|
|
683
|
+
if (param.#used) {
|
|
684
|
+
throw new Error("response outparam already set");
|
|
685
|
+
}
|
|
686
|
+
param.#used = true;
|
|
687
|
+
if (response.tag === "ok") {
|
|
688
|
+
void outgoingResponseToResponse(response.val).then(param.#resolve, param.#reject);
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
param.#resolve(new Response(`WASI HTTP handler error: ${JSON.stringify(response.val)}`, {
|
|
692
|
+
status: 500,
|
|
693
|
+
}));
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
static _isUsed(param) {
|
|
697
|
+
return param.#used;
|
|
698
|
+
}
|
|
699
|
+
static _create() {
|
|
700
|
+
const param = new ResponseOutparam();
|
|
701
|
+
const response = new Promise((resolve, reject) => {
|
|
702
|
+
param.#resolve = resolve;
|
|
703
|
+
param.#reject = reject;
|
|
704
|
+
});
|
|
705
|
+
return [param, response];
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
const responseOutparamCreate = ResponseOutparam._create;
|
|
709
|
+
// @ts-expect-error - Deleting static method
|
|
710
|
+
delete ResponseOutparam._create;
|
|
711
|
+
const responseOutparamIsUsed = ResponseOutparam._isUsed;
|
|
712
|
+
// @ts-expect-error - Deleting static method
|
|
713
|
+
delete ResponseOutparam._isUsed;
|
|
714
|
+
class FutureTrailers {
|
|
715
|
+
#requested = false;
|
|
716
|
+
subscribe() {
|
|
717
|
+
return pollableCreate();
|
|
718
|
+
}
|
|
719
|
+
get() {
|
|
720
|
+
if (this.#requested) {
|
|
721
|
+
return { tag: "err", val: undefined };
|
|
722
|
+
}
|
|
723
|
+
this.#requested = true;
|
|
724
|
+
return {
|
|
725
|
+
tag: "ok",
|
|
726
|
+
val: {
|
|
727
|
+
tag: "ok",
|
|
728
|
+
val: fieldsLock(fieldsFromEntriesChecked([])),
|
|
729
|
+
},
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
static _create() {
|
|
733
|
+
return new FutureTrailers();
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
const futureTrailersCreate = FutureTrailers._create;
|
|
737
|
+
// @ts-expect-error - Deleting static method
|
|
738
|
+
delete FutureTrailers._create;
|
|
739
|
+
function mapFetchError(err) {
|
|
740
|
+
if (err.name === "AbortError") {
|
|
741
|
+
return { tag: "connection-timeout" };
|
|
742
|
+
}
|
|
743
|
+
return { tag: "internal-error", val: err.message };
|
|
744
|
+
}
|
|
745
|
+
class FutureIncomingResponse {
|
|
746
|
+
#result = undefined;
|
|
747
|
+
#promise = null;
|
|
748
|
+
#controller = null;
|
|
749
|
+
#settled = false;
|
|
750
|
+
subscribe() {
|
|
751
|
+
return pollableCreate(this.#promise);
|
|
752
|
+
}
|
|
753
|
+
get() {
|
|
754
|
+
if (this.#result === undefined) {
|
|
755
|
+
return undefined;
|
|
756
|
+
}
|
|
757
|
+
const result = this.#result;
|
|
758
|
+
this.#result = { tag: "err" };
|
|
759
|
+
// The returned response now owns the body. Dropping this consumed future
|
|
760
|
+
// must not abort a Fetch body that the guest is still streaming.
|
|
761
|
+
if (result.tag === "ok" && result.val.tag === "ok") {
|
|
762
|
+
this.#controller = null;
|
|
763
|
+
}
|
|
764
|
+
return result;
|
|
765
|
+
}
|
|
766
|
+
[symbolDispose]() {
|
|
767
|
+
// Only abort if the request never settled. If fetch already
|
|
768
|
+
// resolved (even with an ok response whose body is still being
|
|
769
|
+
// streamed elsewhere), aborting here kills the underlying
|
|
770
|
+
// connection and body stream out from under whoever is still
|
|
771
|
+
// reading it, surfacing as a bogus "BodyStreamBuffer was aborted"
|
|
772
|
+
// error on a request that actually succeeded.
|
|
773
|
+
if (!this.#settled) {
|
|
774
|
+
this.#controller?.abort();
|
|
775
|
+
}
|
|
776
|
+
this.#controller = null;
|
|
777
|
+
this.#promise = null;
|
|
778
|
+
}
|
|
779
|
+
static _create(url, method, headers, bodyData, timeoutMs) {
|
|
780
|
+
const future = new FutureIncomingResponse();
|
|
781
|
+
const controller = new AbortController();
|
|
782
|
+
future.#controller = controller;
|
|
783
|
+
let timer;
|
|
784
|
+
if (timeoutMs < Infinity) {
|
|
785
|
+
timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
786
|
+
}
|
|
787
|
+
future.#promise = Promise.resolve(bodyData)
|
|
788
|
+
.then((bodyData) => {
|
|
789
|
+
const init = {
|
|
790
|
+
method,
|
|
791
|
+
headers,
|
|
792
|
+
signal: controller.signal,
|
|
793
|
+
};
|
|
794
|
+
if (bodyData && method !== "GET" && method !== "HEAD") {
|
|
795
|
+
init.body = bodyData;
|
|
796
|
+
if (bodyData instanceof ReadableStream) {
|
|
797
|
+
init.duplex = "half";
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
return globalThis.fetch(url, init);
|
|
801
|
+
})
|
|
802
|
+
.then((response) => {
|
|
803
|
+
if (timer) {
|
|
804
|
+
clearTimeout(timer);
|
|
805
|
+
}
|
|
806
|
+
future.#settled = true;
|
|
807
|
+
future.#result = {
|
|
808
|
+
tag: "ok",
|
|
809
|
+
val: {
|
|
810
|
+
tag: "ok",
|
|
811
|
+
val: incomingResponseCreate(response),
|
|
812
|
+
},
|
|
813
|
+
};
|
|
814
|
+
}, (err) => {
|
|
815
|
+
if (timer) {
|
|
816
|
+
clearTimeout(timer);
|
|
817
|
+
}
|
|
818
|
+
future.#settled = true;
|
|
819
|
+
future.#result = {
|
|
820
|
+
tag: "ok",
|
|
821
|
+
val: {
|
|
822
|
+
tag: "err",
|
|
823
|
+
val: mapFetchError(err),
|
|
824
|
+
},
|
|
825
|
+
};
|
|
826
|
+
});
|
|
827
|
+
return future;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
const futureIncomingResponseCreate = FutureIncomingResponse._create;
|
|
831
|
+
// @ts-expect-error - Deleting static method
|
|
832
|
+
delete FutureIncomingResponse._create;
|
|
833
|
+
function schemeString(scheme) {
|
|
834
|
+
if (!scheme) {
|
|
835
|
+
return "https:";
|
|
836
|
+
}
|
|
837
|
+
switch (scheme.tag) {
|
|
838
|
+
case "HTTP":
|
|
839
|
+
return "http:";
|
|
840
|
+
case "HTTPS":
|
|
841
|
+
return "https:";
|
|
842
|
+
case "other":
|
|
843
|
+
return scheme.val.toLowerCase() + ":";
|
|
844
|
+
}
|
|
845
|
+
return "https:";
|
|
846
|
+
}
|
|
847
|
+
function httpErrorCode(err) {
|
|
848
|
+
if ("payload" in err) {
|
|
849
|
+
return err.payload;
|
|
850
|
+
}
|
|
851
|
+
return {
|
|
852
|
+
tag: "internal-error",
|
|
853
|
+
val: "message" in err ? err.message : err.toDebugString(),
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
let requestStreamingEnabled = false;
|
|
857
|
+
/** Enable or disable Fetch `ReadableStream` request bodies. Disabled by default. */
|
|
858
|
+
export function _setRequestStreaming(enabled) {
|
|
859
|
+
if (typeof enabled !== "boolean") {
|
|
860
|
+
throw new TypeError("request streaming setting must be a boolean");
|
|
861
|
+
}
|
|
862
|
+
requestStreamingEnabled = enabled;
|
|
863
|
+
}
|
|
864
|
+
export const outgoingHandler = {
|
|
865
|
+
handle(request, options) {
|
|
866
|
+
return outgoingRequestHandle(request, options, {
|
|
867
|
+
streamingRequestBodies: requestStreamingEnabled,
|
|
868
|
+
});
|
|
869
|
+
},
|
|
870
|
+
};
|
|
871
|
+
export const incomingHandler = {
|
|
872
|
+
handle() {
|
|
873
|
+
throw "not-supported";
|
|
874
|
+
},
|
|
875
|
+
};
|
|
876
|
+
/** Create a `wasi:http/incoming-handler` namespace backed by a Web Request handler. */
|
|
877
|
+
export function createIncomingHandler(handler) {
|
|
878
|
+
return {
|
|
879
|
+
async handle(request, responseOut) {
|
|
880
|
+
try {
|
|
881
|
+
const response = await handler(incomingRequestToRequest(request));
|
|
882
|
+
if (!(response instanceof Response)) {
|
|
883
|
+
throw new TypeError("incoming HTTP handler must return a Response");
|
|
884
|
+
}
|
|
885
|
+
ResponseOutparam.set(responseOut, {
|
|
886
|
+
tag: "ok",
|
|
887
|
+
val: await responseToOutgoingResponse(response),
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
catch (error) {
|
|
891
|
+
ResponseOutparam.set(responseOut, {
|
|
892
|
+
tag: "err",
|
|
893
|
+
val: {
|
|
894
|
+
tag: "internal-error",
|
|
895
|
+
val: error instanceof Error ? error.message : String(error),
|
|
896
|
+
},
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
},
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
async function responseToOutgoingResponse(response) {
|
|
903
|
+
const headers = [];
|
|
904
|
+
response.headers.forEach((value, name) => headers.push([name, utf8Encoder.encode(value)]));
|
|
905
|
+
const outgoing = new OutgoingResponse(fieldsFromEntriesChecked(headers));
|
|
906
|
+
outgoing.setStatusCode(response.status);
|
|
907
|
+
if (response.body) {
|
|
908
|
+
const body = outgoing.body();
|
|
909
|
+
const stream = body.write();
|
|
910
|
+
const reader = response.body.getReader();
|
|
911
|
+
for (let result = await reader.read(); !result.done; result = await reader.read()) {
|
|
912
|
+
let offset = 0;
|
|
913
|
+
while (offset < result.value.byteLength) {
|
|
914
|
+
const permit = stream.checkWrite();
|
|
915
|
+
if (permit === 0n) {
|
|
916
|
+
await stream.subscribe().block();
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
const length = Math.min(Number(permit), result.value.byteLength - offset);
|
|
920
|
+
stream.write(result.value.subarray(offset, offset + length));
|
|
921
|
+
offset += length;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
OutgoingBody.finish(body, undefined);
|
|
925
|
+
}
|
|
926
|
+
return outgoing;
|
|
927
|
+
}
|
|
928
|
+
/** Translate a browser Request through a WASI handler with a synchronously readable body. */
|
|
929
|
+
export async function handleIncomingRequest(request, handler) {
|
|
930
|
+
const [responseOut, response] = responseOutparamCreate();
|
|
931
|
+
const body = request.body
|
|
932
|
+
? new Uint8Array(await request.clone().arrayBuffer())
|
|
933
|
+
: new Uint8Array();
|
|
934
|
+
await handler(incomingRequestCreate(request, body), responseOut);
|
|
935
|
+
if (!responseOutparamIsUsed(responseOut)) {
|
|
936
|
+
throw new Error("WASI HTTP handler returned without setting its response outparam");
|
|
937
|
+
}
|
|
938
|
+
return response;
|
|
939
|
+
}
|
|
940
|
+
export const types = {
|
|
941
|
+
Fields,
|
|
942
|
+
FutureIncomingResponse,
|
|
943
|
+
FutureTrailers,
|
|
944
|
+
IncomingBody,
|
|
945
|
+
IncomingRequest,
|
|
946
|
+
IncomingResponse,
|
|
947
|
+
OutgoingBody,
|
|
948
|
+
OutgoingRequest,
|
|
949
|
+
OutgoingResponse,
|
|
950
|
+
ResponseOutparam,
|
|
951
|
+
RequestOptions,
|
|
952
|
+
httpErrorCode,
|
|
953
|
+
};
|