@bytecodealliance/preview2-shim 0.0.16 → 0.0.17

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.
@@ -1,14 +1,9 @@
1
- // Based on:
2
- // https://github.com/bytecodealliance/wasmtime/blob/8efcb9851602287fd07a1a1e91501f51f2653d7e/crates/wasi-http/
3
-
4
1
  /**
5
2
  * @typedef {import("../../types/interfaces/wasi-http-types").Fields} Fields
6
3
  * @typedef {import("../../types/interfaces/wasi-http-types").FutureIncomingResponse} FutureIncomingResponse
7
4
  * @typedef {import("../../types/interfaces/wasi-http-types").Headers} Headers
8
- * @typedef {import("../../types/interfaces/wasi-http-types").IncomingResponse} IncomingResponse
9
5
  * @typedef {import("../../types/interfaces/wasi-http-types").IncomingStream} IncomingStream
10
6
  * @typedef {import("../../types/interfaces/wasi-http-types").Method} Method
11
- * @typedef {import("../../types/interfaces/wasi-http-types").OutgoingRequest} OutgoingRequest
12
7
  * @typedef {import("../../types/interfaces/wasi-http-types").RequestOptions} RequestOptions
13
8
  * @typedef {import("../../types/interfaces/wasi-http-types").Result} Result
14
9
  * @typedef {import("../../types/interfaces/wasi-http-types").Scheme} Scheme
@@ -16,355 +11,372 @@
16
11
  * @typedef {import("../../types/interfaces/wasi-io-streams").StreamStatus} StreamStatus
17
12
  */
18
13
 
19
- import * as io from '@bytecodealliance/preview2-shim/io';
20
- import * as http from '@bytecodealliance/preview2-shim/http';
21
- import { UnexpectedError } from './error.js';
22
-
23
- export class WasiHttp {
24
- requestIdBase = 1;
25
- responseIdBase = 1;
26
- fieldsIdBase = 1;
27
- streamIdBase = 3;
28
- futureIdBase = 1;
29
- /** @type {Map<number,ActiveRequest>} */ requests = new Map();
30
- /** @type {Map<number,ActiveResponse>} */ responses = new Map();
31
- /** @type {Map<number,ActiveFields>} */ fields = new Map();
32
- /** @type {Map<number,Uint8Array>} */ streams = new Map();
33
- /** @type {Map<number,ActiveFuture>} */ futures = new Map();
34
-
35
- constructor() {}
36
-
37
- /**
38
- * @param {OutgoingRequest} requestId
39
- * @param {RequestOptions | null} options
40
- * @returns {FutureIncomingResponse}
41
- */
42
- handle = (requestId, _options) => {
43
- const request = this.requests.get(requestId);
44
- if (!request) throw Error("not found!");
14
+ import { fileURLToPath } from 'node:url';
15
+ import { createSyncFn } from './synckit/index.js';
45
16
 
46
- const responseId = this.responseIdBase;
47
- this.responseIdBase += 1;
48
- const response = new ActiveResponse(responseId);
17
+ const workerPath = fileURLToPath(new URL('./make-request.js', import.meta.url));
49
18
 
50
- const scheme = request.scheme.tag === "HTTP" ? "http://" : "https://";
51
-
52
- const url = scheme + request.authority + request.pathWithQuery;
53
- const headers = {
54
- "host": request.authority,
19
+ function send(req) {
20
+ const syncFn = createSyncFn(workerPath);
21
+ let rawResponse = syncFn(req);
22
+ let response = JSON.parse(rawResponse);
23
+ if (response.status) {
24
+ return {
25
+ ...response,
26
+ body: response.body ? Buffer.from(response.body, 'base64') : undefined,
55
27
  };
56
- if (request.headers) {
57
- const requestHeaders = this.fields.get(request.headers);
58
- const decoder = new TextDecoder();
59
- for (const [key, value] of requestHeaders.fields.entries()) {
60
- headers[key] = decoder.decode(value);
61
- }
62
- }
63
- const body = this.streams.get(request.body);
64
-
65
- const res = http.send({
66
- method: request.method.tag,
67
- uri: url,
68
- headers: headers,
69
- params: [],
70
- body: body && body.length > 0 ? body : undefined,
71
- });
72
-
73
- response.status = res.status;
74
- if (res.headers && res.headers.length > 0) {
75
- response.headers = this.newFields(res.headers);
76
- }
77
- const buf = res.body;
78
- response.body = this.streamIdBase;
79
- this.streamIdBase += 1;
80
- this.streams.set(response.body, buf);
81
- this.responses.set(responseId, response);
82
-
83
- const futureId = this.futureIdBase;
84
- this.futureIdBase += 1;
85
- const future = new ActiveFuture(futureId, responseId);
86
- this.futures.set(futureId, future);
87
- return futureId;
88
- }
89
-
90
- read = (stream, len) => {
91
- return this.blockingRead(stream, len);
92
- }
93
-
94
- /**
95
- * @param {InputStream} stream
96
- * @param {bigint} len
97
- * @returns {[Uint8Array | ArrayBuffer, StreamStatus]}
98
- */
99
- blockingRead = (stream, len) => {
100
- if (stream < 3) {
101
- return io.streams.blockingRead(stream);
102
- }
103
- const s = this.streams.get(stream);
104
- if (!s) throw Error(`stream not found: ${stream}`);
105
- const position = Number(len);
106
- if (position === 0) {
107
- return [new Uint8Array(), s.byteLength > 0 ? 'open' : 'ended'];
108
- } else if (s.byteLength > position) {
109
- this.streams.set(stream, s.slice(position, s.byteLength));
110
- return [s.slice(0, position), 'open'];
111
- } else {
112
- return [s.slice(0, position), 'ended'];
113
- }
114
28
  }
29
+ throw new UnexpectedError(response.message);
30
+ }
115
31
 
116
- /**
117
- * @param {InputStream} stream
118
- * @returns {Pollable}
119
- */
120
- subscribeToInputStream = (stream) => {
121
- // TODO: not implemented yet
122
- console.log(`[streams] Subscribe to input stream ${stream}`);
32
+ class UnexpectedError extends Error {
33
+ payload;
34
+ constructor(message = "unexpected-error") {
35
+ super(message);
36
+ this.payload = {
37
+ tag: "unexpected-error",
38
+ val: message,
39
+ };
123
40
  }
41
+ }
124
42
 
125
- /**
126
- * @param {InputStream} stream
127
- */
128
- dropInputStream = (stream) => {
129
- const s = this.streams.get(stream);
130
- if (!s) throw Error(`no such input stream ${stream}`);
131
- s.set([]);
43
+ function combineChunks (chunks) {
44
+ if (chunks.length === 0)
45
+ return new Uint8Array();
46
+ if (chunks.length === 1)
47
+ return chunks[0];
48
+ const totalLen = chunks.reduce((total, chunk) => total + chunk.byteLength);
49
+ const out = new Uint8Array(totalLen);
50
+ let idx = 0;
51
+ for (const chunk of chunks) {
52
+ out.set(chunk, idx);
53
+ idx += chunk.byteLength;
132
54
  }
55
+ return out;
56
+ }
133
57
 
134
- checkWrite = (stream) => {
135
- // TODO: implement
136
- return io.streams.checkWrite(stream);
137
- }
58
+ export class WasiHttp {
59
+ requestCnt = 1;
60
+ responseCnt = 1;
61
+ fieldsCnt = 1;
62
+ futureCnt = 1;
63
+ /** @type {Map<number,OutgoingRequest>} */ requests = new Map();
64
+ /** @type {Map<number,IncomingResponse>} */ responses = new Map();
65
+ /** @type {Map<number,Fields>} */ fields = new Map();
66
+ /** @type {Map<number,Future>} */ futures = new Map();
138
67
 
139
68
  /**
140
- * @param {OutputStream} stream
141
- * @param {Uint8Array} buf
69
+ *
70
+ * @param {import('../common/io.js').Io} io
71
+ * @returns
142
72
  */
143
- write = (stream, buf) => {
144
- if (stream < 3) {
145
- return io.streams.write(stream, buf);
146
- }
147
- this.streams.set(stream, buf);
148
- }
149
-
150
- blockingWriteAndFlush = (stream, buf) => {
151
- if (stream < 3) {
152
- return io.streams.blockingWriteAndFlush(stream, buf);
73
+ constructor(io) {
74
+ const http = this;
75
+
76
+ class OutgoingRequest {
77
+ /** @type {number} */ id;
78
+ bodyFinished = false;
79
+ /** @type {Method} */ method = { tag: 'get' };
80
+ /** @type {Scheme | undefined} */ scheme = { tag: 'HTTP' };
81
+ pathWithQuery = undefined;
82
+ authority = undefined;
83
+ /** @type {number | undefined} */ headers = undefined;
84
+ chunks = [];
85
+ body = io.createStream(this);
86
+ constructor() {
87
+ http.requests.set(this.id = http.requestCnt++, this);
88
+ }
89
+ write (bytes) {
90
+ this.chunks.push(bytes);
91
+ }
153
92
  }
154
- // TODO: implement
155
- }
156
-
157
- flush = (stream) => {
158
- return this.blockingFlush(stream);
159
- }
160
-
161
- blockingFlush = (stream) => {
162
- if (stream < 3) {
163
- return io.streams.blockingFlush(stream);
93
+ class IncomingResponse {
94
+ /** @type {number} */ id;
95
+ bodyFinished = false;
96
+ status = 0;
97
+ chunks = [];
98
+ body = io.createStream(this);
99
+ /** @type {number | undefined} */ headers = undefined;
100
+ constructor() {
101
+ http.responses.set(this.id = http.responseCnt++, this);
102
+ }
103
+ read (_len) {
104
+ if (this.chunks.length === 0)
105
+ return [new Uint8Array([]), this.bodyFinished ? 'ended' : 'open'];
106
+ if (this.chunks.length === 1)
107
+ return [this.chunks[0], this.bodyFinished ? 'ended' : 'open'];
108
+ return [this.chunks.shift(), 'open'];
109
+ }
164
110
  }
165
- // TODO: implement
166
- }
167
-
168
- /**
169
- * @param {OutputStream} stream
170
- * @returns {Pollable}
171
- */
172
- subscribeToOutputStream = (stream) => {
173
- // TODO: not implemented yet
174
- console.log(`[streams] Subscribe to output stream ${stream}`);
175
- }
176
-
177
- /**
178
- * @param {OutputStream} stream
179
- */
180
- dropOutputStream = (stream) => {
181
- const s = this.streams.get(stream);
182
- if (!s) throw Error(`no such output stream ${stream}`);
183
- s.set([]);
184
- }
185
-
186
- /**
187
- * @param {Fields} fields
188
- */
189
- dropFields = (fields) => {
190
- this.fields.delete(fields);
191
- }
192
-
193
- /**
194
- * @param {[string, string][]} entries
195
- * @returns {Fields}
196
- */
197
- newFields = (entries) => {
198
- const id = this.fieldsIdBase;
199
- this.fieldsIdBase += 1;
200
- this.fields.set(id, new ActiveFields(id, entries));
201
-
202
- return id;
203
- }
204
111
 
205
- /**
206
- * @param {Fields} fields
207
- * @returns {[string, Uint8Array][]}
208
- */
209
- fieldsEntries = (fields) => {
210
- const activeFields = this.fields.get(fields);
211
- return activeFields ? Array.from(activeFields.fields) : [];
212
- }
213
-
214
- /**
215
- * @param {OutgoingRequest} request
216
- */
217
- dropOutgoingRequest = (request) => {
218
- this.requests.delete(request);
219
- }
220
-
221
- /**
222
- * @param {Method} method
223
- * @param {string | null} pathWithQuery
224
- * @param {Scheme | null} scheme
225
- * @param {string | null} authority
226
- * @param {Headers} headers
227
- * @returns {number}
228
- */
229
- newOutgoingRequest = (method, pathWithQuery, scheme, authority, headers) => {
230
- const id = this.requestIdBase;
231
- this.requestIdBase += 1;
232
-
233
- const req = new ActiveRequest(id);
234
- req.pathWithQuery = pathWithQuery;
235
- req.authority = authority;
236
- req.method = method;
237
- req.headers = headers;
238
- req.scheme = scheme;
239
- this.requests.set(id, req);
240
- return id;
241
- }
242
-
243
- /**
244
- * @param {OutgoingRequest} request
245
- * @returns {OutgoingStream}
246
- */
247
- outgoingRequestWrite = (request) => {
248
- const req = this.requests.get(request);
249
- req.body = this.streamIdBase;
250
- this.streamIdBase += 1;
251
- return req.body;
252
- }
253
-
254
- /**
255
- * @param {IncomingResponse} response
256
- */
257
- dropIncomingResponse = (response) => {
258
- this.responses.delete(response);
259
- }
260
-
261
- /**
262
- * @param {IncomingResponse} response
263
- * @returns {StatusCode}
264
- */
265
- incomingResponseStatus = (response) => {
266
- const r = this.responses.get(response);
267
- return r.status;
268
- }
269
-
270
- /**
271
- * @param {IncomingResponse} response
272
- * @returns {Headers}
273
- */
274
- incomingResponseHeaders = (response) => {
275
- const r = this.responses.get(response);
276
- return r.headers ?? 0;
277
- }
278
-
279
- /**
280
- * @param {IncomingResponse} response
281
- * @returns {IncomingStream}
282
- */
283
- incomingResponseConsume = (response) => {
284
- const r = this.responses.get(response);
285
- return r.body;
286
- }
287
-
288
- /**
289
- * @param {FutureIncomingResponse} future
290
- */
291
- dropFutureIncomingResponse = (future) => {
292
- return this.futures.delete(future);
293
- }
294
-
295
- /**
296
- * @param {FutureIncomingResponse} future
297
- * @returns {Result<IncomingResponse, Error> | null}
298
- */
299
- futureIncomingResponseGet = (future) => {
300
- const f = this.futures.get(future);
301
- if (!f) {
302
- return {
303
- tag: "err",
304
- val: UnexpectedError(`no such future ${f}`),
305
- };
112
+ class Future {
113
+ /** @type {number} */ id;
114
+ /** @type {number} */ responseId;
115
+
116
+ constructor(responseId) {
117
+ http.futures.set(this.id = http.futureCnt++, this);
118
+ this.responseId = responseId;
119
+ }
306
120
  }
307
- // For now this will assume the future will return
308
- // the response immediately
309
- const response = f.responseId;
310
- const r = this.responses.get(response);
311
- if (!r) {
312
- return {
313
- tag: "err",
314
- val: UnexpectedError(`no such response ${response}`),
315
- };
121
+
122
+ class Fields {
123
+ /** @type {number} */ id;
124
+ /** @type {Map<string, Uint8Array[]>} */ fields;
125
+
126
+ constructor(fields) {
127
+ http.fields.set(this.id = http.fieldsCnt++, this);
128
+ const encoder = new TextEncoder();
129
+ this.fields = new Map(fields.map(([k, v]) => [k, encoder.encode(v)]));
130
+ }
316
131
  }
317
- return {
318
- tag: "ok",
319
- val: response,
320
- };
321
- }
322
- }
323
132
 
324
- class ActiveRequest {
325
- /** @type {number} */ id;
326
- activeRequest = false;
327
- /** @type {Method} */ method = { tag: 'get' };
328
- /** @type {Scheme | null} */ scheme = { tag: 'HTTP' };
329
- pathWithQuery = null;
330
- authority = null;
331
- /** @type {number | null} */ headers = null;
332
- body = 3;
333
-
334
- constructor(id) {
335
- this.id = id;
336
- }
337
- }
338
-
339
- class ActiveResponse {
340
- /** @type {number} */ id;
341
- activeResponse = false;
342
- status = 0;
343
- body = 3;
344
- /** @type {number | null} */ headers = null;
133
+ this.incomingHandler = {
134
+ // TODO
135
+ handle () {
345
136
 
346
- constructor(id) {
347
- this.id = id;
348
- }
349
- }
350
-
351
- class ActiveFuture {
352
- /** @type {number} */ id;
353
- /** @type {number} */ responseId;
354
-
355
- constructor(id, responseId) {
356
- this.id = id;
357
- this.responseId = responseId;
358
- }
359
- }
137
+ }
138
+ };
360
139
 
361
- class ActiveFields {
362
- /** @type {number} */ id;
363
- /** @type {Map<string, Uint8Array[]>} */ fields;
140
+ this.outgoingHandler = {
141
+ /**
142
+ * @param {OutgoingRequest} requestId
143
+ * @param {RequestOptions | undefined} options
144
+ * @returns {FutureIncomingResponse}
145
+ */
146
+ handle (requestId, _options) {
147
+ const request = http.requests.get(requestId);
148
+ if (!request) throw Error("not found!");
149
+
150
+ const response = new IncomingResponse();
151
+
152
+ const scheme = request.scheme.tag === "HTTP" ? "http://" : "https://";
153
+
154
+ const url = scheme + request.authority + request.pathWithQuery;
155
+ const headers = {
156
+ "host": request.authority,
157
+ };
158
+ if (request.headers) {
159
+ const requestHeaders = http.fields.get(request.headers);
160
+ const decoder = new TextDecoder();
161
+ for (const [key, value] of requestHeaders.fields.entries()) {
162
+ headers[key] = decoder.decode(value);
163
+ }
164
+ }
165
+
166
+ const res = send({
167
+ method: request.method.tag,
168
+ uri: url,
169
+ headers: headers,
170
+ params: [],
171
+ body: combineChunks(request.chunks),
172
+ });
173
+
174
+ response.status = res.status;
175
+ if (res.headers && res.headers.length > 0) {
176
+ response.headers = types.newFields(res.headers);
177
+ }
178
+ http.responses.set(response.id, response);
179
+ response.chunks = [res.body];
180
+
181
+ const future = new Future(response.id);
182
+ return future.id;
183
+ }
184
+ };
364
185
 
365
- constructor(id, fields) {
366
- this.id = id;
367
- const encoder = new TextEncoder();
368
- this.fields = new Map(fields.map(([k, v]) => [k, encoder.encode(v)]));
186
+ const types = this.types = {
187
+ /**
188
+ * @param {Fields} fields
189
+ */
190
+ dropFields(fields) {
191
+ http.fields.delete(fields);
192
+ },
193
+
194
+ /**
195
+ * @param {[string, string][]} entries
196
+ * @returns {Fields}
197
+ */
198
+ newFields(entries) {
199
+ return new Fields(entries).id;
200
+ },
201
+
202
+ fieldsGet(_fields, _name) {
203
+ console.log("[types] Fields get");
204
+ },
205
+ fieldsSet(_fields, _name, _value) {
206
+ console.log("[types] Fields set");
207
+ },
208
+ fieldsDelete(_fields, _name) {
209
+ console.log("[types] Fields delete");
210
+ },
211
+ fieldsAppend(_fields, _name, _value) {
212
+ console.log("[types] Fields append");
213
+ },
214
+
215
+ /**
216
+ * @param {Fields} fields
217
+ * @returns {[string, Uint8Array][]}
218
+ */
219
+ fieldsEntries(fields) {
220
+ const activeFields = http.fields.get(fields);
221
+ return activeFields ? Array.from(activeFields.fields) : [];
222
+ },
223
+
224
+ fieldsClone(_fields) {
225
+ console.log("[types] Fields clone");
226
+ },
227
+ finishIncomingStream(s) {
228
+ io.getStream(s).bodyFinished = true;
229
+ },
230
+ finishOutgoingStream(s, _trailers) {
231
+ io.getStream(s).bodyFinished = true;
232
+ },
233
+ dropIncomingRequest(_req) {
234
+ console.log("[types] Drop incoming request");
235
+ },
236
+
237
+ /**
238
+ * @param {OutgoingRequest} request
239
+ */
240
+ dropOutgoingRequest(request) {
241
+ http.requests.delete(request);
242
+ },
243
+
244
+ incomingRequestMethod(_req) {
245
+ console.log("[types] Incoming request method");
246
+ },
247
+ incomingRequestPathWithQuery(_req) {
248
+ console.log("[types] Incoming request path with query");
249
+ },
250
+ incomingRequestScheme(_req) {
251
+ console.log("[types] Incoming request scheme");
252
+ },
253
+ incomingRequestAuthority(_req) {
254
+ console.log("[types] Incoming request authority");
255
+ },
256
+ incomingRequestHeaders(_req) {
257
+ console.log("[types] Incoming request headers");
258
+ },
259
+ incomingRequestConsume(_req) {
260
+ console.log("[types] Incoming request consume");
261
+ },
262
+
263
+ /**
264
+ * @param {Method} method
265
+ * @param {string | undefined} pathWithQuery
266
+ * @param {Scheme | undefined} scheme
267
+ * @param {string | undefined} authority
268
+ * @param {Headers} headers
269
+ * @returns {number}
270
+ */
271
+ newOutgoingRequest(method, pathWithQuery, scheme, authority, headers) {
272
+ const req = new OutgoingRequest();
273
+ req.pathWithQuery = pathWithQuery;
274
+ req.authority = authority;
275
+ req.method = method;
276
+ req.headers = headers;
277
+ req.scheme = scheme;
278
+ return req.id;
279
+ },
280
+
281
+ /**
282
+ * @param {OutgoingRequest} request
283
+ * @returns {OutgoingStream}
284
+ */
285
+ outgoingRequestWrite(request) {
286
+ return http.requests.get(request).body;
287
+ },
288
+
289
+ dropResponseOutparam(_res) {
290
+ console.log("[types] Drop response outparam");
291
+ },
292
+ setResponseOutparam(_response) {
293
+ console.log("[types] Set response outparam");
294
+ },
295
+
296
+ /**
297
+ * @param {IncomingResponse} response
298
+ */
299
+ dropIncomingResponse(response) {
300
+ http.responses.delete(response);
301
+ },
302
+
303
+ dropOutgoingResponse(_res) {
304
+ console.log("[types] Drop outgoing response");
305
+ },
306
+
307
+ /**
308
+ * @param {IncomingResponse} response
309
+ * @returns {StatusCode}
310
+ */
311
+ incomingResponseStatus(response) {
312
+ const r = http.responses.get(response);
313
+ return r.status;
314
+ },
315
+
316
+ /**
317
+ * @param {IncomingResponse} response
318
+ * @returns {Headers}
319
+ */
320
+ incomingResponseHeaders(response) {
321
+ const r = http.responses.get(response);
322
+ return r.headers ?? 0;
323
+ },
324
+
325
+ /**
326
+ * @param {IncomingResponse} response
327
+ * @returns {IncomingStream}
328
+ */
329
+ incomingResponseConsume(response) {
330
+ const r = http.responses.get(response);
331
+ return r.body;
332
+ },
333
+
334
+ newOutgoingResponse(_statusCode, _headers) {
335
+ console.log("[types] New outgoing response");
336
+ },
337
+ outgoingResponseWrite(_res) {
338
+ console.log("[types] Outgoing response write");
339
+ },
340
+
341
+
342
+ /**
343
+ * @param {FutureIncomingResponse} future
344
+ */
345
+ dropFutureIncomingResponse(future) {
346
+ return http.futures.delete(future);
347
+ },
348
+
349
+ /**
350
+ * @param {FutureIncomingResponse} future
351
+ * @returns {Result<IncomingResponse, Error> | undefined}
352
+ */
353
+ futureIncomingResponseGet(future) {
354
+ const f = http.futures.get(future);
355
+ if (!f) {
356
+ return {
357
+ tag: "err",
358
+ val: UnexpectedError(`no such future ${f}`),
359
+ };
360
+ }
361
+ // For now this will assume the future will return
362
+ // the response immediately
363
+ const response = f.responseId;
364
+ const r = http.responses.get(response);
365
+ if (!r) {
366
+ return {
367
+ tag: "err",
368
+ val: UnexpectedError(`no such response ${response}`),
369
+ };
370
+ }
371
+ return {
372
+ tag: "ok",
373
+ val: response,
374
+ };
375
+ },
376
+
377
+ listenToFutureIncomingResponse(_f) {
378
+ console.log("[types] Listen to future incoming response");
379
+ }
380
+ };
369
381
  }
370
382
  }