@bytecodealliance/preview2-shim 0.0.21 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -14
- package/lib/browser/cli.js +2 -4
- package/lib/browser/clocks.js +15 -27
- package/lib/browser/filesystem.js +2 -30
- package/lib/browser/http.js +1 -3
- package/lib/browser/io.js +4 -2
- package/lib/common/assert.js +7 -0
- package/lib/io/calls.js +64 -0
- package/lib/io/worker-http.js +95 -0
- package/lib/io/worker-io.js +322 -0
- package/lib/io/worker-thread.js +569 -0
- package/lib/nodejs/cli.js +45 -59
- package/lib/nodejs/clocks.js +13 -27
- package/lib/nodejs/filesystem.js +539 -459
- package/lib/nodejs/http.js +440 -173
- package/lib/nodejs/index.js +4 -1
- package/lib/nodejs/io.js +1 -0
- package/lib/nodejs/sockets/socket-common.js +116 -0
- package/lib/nodejs/sockets/socketopts-bindings.js +94 -0
- package/lib/nodejs/sockets/tcp-socket-impl.js +794 -0
- package/lib/nodejs/sockets/udp-socket-impl.js +628 -0
- package/lib/nodejs/sockets/wasi-sockets.js +320 -0
- package/lib/nodejs/sockets.js +11 -200
- package/lib/synckit/index.js +4 -2
- package/package.json +1 -5
- package/types/interfaces/wasi-cli-terminal-input.d.ts +4 -0
- package/types/interfaces/wasi-cli-terminal-output.d.ts +4 -0
- package/types/interfaces/wasi-clocks-monotonic-clock.d.ts +19 -6
- package/types/interfaces/wasi-filesystem-types.d.ts +1 -178
- package/types/interfaces/wasi-http-outgoing-handler.d.ts +2 -2
- package/types/interfaces/wasi-http-types.d.ts +412 -82
- package/types/interfaces/wasi-io-error.d.ts +16 -0
- package/types/interfaces/wasi-io-poll.d.ts +19 -8
- package/types/interfaces/wasi-io-streams.d.ts +26 -46
- package/types/interfaces/wasi-sockets-ip-name-lookup.d.ts +9 -21
- package/types/interfaces/wasi-sockets-network.d.ts +4 -0
- package/types/interfaces/wasi-sockets-tcp.d.ts +75 -18
- package/types/interfaces/wasi-sockets-udp-create-socket.d.ts +1 -1
- package/types/interfaces/wasi-sockets-udp.d.ts +282 -193
- package/types/wasi-cli-command.d.ts +28 -28
- package/types/wasi-http-proxy.d.ts +12 -12
- package/lib/common/io.js +0 -183
- package/lib/common/make-request.js +0 -30
- package/types/interfaces/wasi-clocks-timezone.d.ts +0 -56
package/lib/nodejs/http.js
CHANGED
|
@@ -1,46 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
INPUT_STREAM_DISPOSE,
|
|
3
|
+
HTTP_CREATE_REQUEST,
|
|
4
|
+
HTTP_OUTPUT_STREAM_FINISH,
|
|
5
|
+
OUTPUT_STREAM_CREATE,
|
|
6
|
+
FUTURE_GET_VALUE_AND_DISPOSE,
|
|
7
|
+
FUTURE_DISPOSE,
|
|
8
|
+
} from "../io/calls.js";
|
|
9
|
+
import {
|
|
10
|
+
ioCall,
|
|
11
|
+
pollableCreate,
|
|
12
|
+
inputStreamCreate,
|
|
13
|
+
outputStreamCreate,
|
|
14
|
+
outputStreamId,
|
|
15
|
+
} from "../io/worker-io.js";
|
|
16
|
+
import { validateHeaderName, validateHeaderValue } from "node:http";
|
|
4
17
|
|
|
5
|
-
|
|
18
|
+
import { HTTP } from "../io/calls.js";
|
|
19
|
+
|
|
20
|
+
const symbolDispose = Symbol.dispose || Symbol.for("dispose");
|
|
21
|
+
export const _forbiddenHeaders = new Set(["connection", "keep-alive"]);
|
|
6
22
|
|
|
7
23
|
/**
|
|
8
24
|
* @typedef {import("../../types/interfaces/wasi-http-types").Method} Method
|
|
9
|
-
* @typedef {import("../../types/interfaces/wasi-http-types").RequestOptions} RequestOptions
|
|
10
25
|
* @typedef {import("../../types/interfaces/wasi-http-types").Scheme} Scheme
|
|
11
26
|
* @typedef {import("../../types/interfaces/wasi-http-types").Error} HttpError
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const workerPath = fileURLToPath(new URL('../common/make-request.js', import.meta.url));
|
|
15
|
-
|
|
16
|
-
function send(req) {
|
|
17
|
-
const syncFn = createSyncFn(workerPath);
|
|
18
|
-
let rawResponse = syncFn(req);
|
|
19
|
-
let response = JSON.parse(rawResponse);
|
|
20
|
-
if (response.status) {
|
|
21
|
-
return {
|
|
22
|
-
...response,
|
|
23
|
-
body: response.body ? Buffer.from(response.body, 'base64') : undefined,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
// HttpError
|
|
27
|
-
throw { tag: 'unexpected-error', val: response.message };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function combineChunks (chunks) {
|
|
31
|
-
if (chunks.length === 0)
|
|
32
|
-
return new Uint8Array();
|
|
33
|
-
if (chunks.length === 1)
|
|
34
|
-
return chunks[0];
|
|
35
|
-
const totalLen = chunks.reduce((total, chunk) => total + chunk.byteLength);
|
|
36
|
-
const out = new Uint8Array(totalLen);
|
|
37
|
-
let idx = 0;
|
|
38
|
-
for (const chunk of chunks) {
|
|
39
|
-
out.set(chunk, idx);
|
|
40
|
-
idx += chunk.byteLength;
|
|
41
|
-
}
|
|
42
|
-
return out;
|
|
43
|
-
}
|
|
27
|
+
*/
|
|
44
28
|
|
|
45
29
|
export class WasiHttp {
|
|
46
30
|
requestCnt = 1;
|
|
@@ -52,49 +36,84 @@ export class WasiHttp {
|
|
|
52
36
|
const http = this;
|
|
53
37
|
|
|
54
38
|
class IncomingBody {
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
stream
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
throw { tag: 'closed' };
|
|
63
|
-
if (incomingBody.#chunks.length === 0)
|
|
64
|
-
return new Uint8Array([]);
|
|
65
|
-
// TODO: handle chunk splitting case where len is less than chunk length
|
|
66
|
-
return incomingBody.#chunks.shift();
|
|
67
|
-
},
|
|
68
|
-
subscribe () {
|
|
69
|
-
// TODO
|
|
70
|
-
}
|
|
71
|
-
});
|
|
39
|
+
#finished = false;
|
|
40
|
+
#streamId = undefined;
|
|
41
|
+
stream() {
|
|
42
|
+
if (!this.#streamId) throw undefined;
|
|
43
|
+
const streamId = this.#streamId;
|
|
44
|
+
this.#streamId = undefined;
|
|
45
|
+
return inputStreamCreate(HTTP, streamId);
|
|
72
46
|
}
|
|
73
|
-
static finish
|
|
74
|
-
incomingBody.#
|
|
47
|
+
static finish(incomingBody) {
|
|
48
|
+
if (incomingBody.#finished)
|
|
49
|
+
throw new Error("incoming body already finished");
|
|
50
|
+
incomingBody.#finished = true;
|
|
75
51
|
return futureTrailersCreate(new Fields([]), false);
|
|
76
52
|
}
|
|
77
|
-
|
|
78
|
-
|
|
53
|
+
[symbolDispose]() {
|
|
54
|
+
if (!this.#finished) {
|
|
55
|
+
ioCall(INPUT_STREAM_DISPOSE | HTTP, this.#streamId);
|
|
56
|
+
this.#streamId = undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
static _create(streamId) {
|
|
60
|
+
const incomingBody = new IncomingBody();
|
|
61
|
+
incomingBody.#streamId = streamId;
|
|
62
|
+
return incomingBody;
|
|
79
63
|
}
|
|
80
64
|
}
|
|
81
|
-
const
|
|
82
|
-
delete IncomingBody.
|
|
65
|
+
const incomingBodyCreate = IncomingBody._create;
|
|
66
|
+
delete IncomingBody._create;
|
|
83
67
|
|
|
84
|
-
|
|
85
|
-
|
|
68
|
+
class IncomingRequest {
|
|
69
|
+
#method;
|
|
70
|
+
#pathWithQuery;
|
|
71
|
+
#scheme;
|
|
72
|
+
#authority;
|
|
73
|
+
#headers;
|
|
74
|
+
#streamId;
|
|
75
|
+
method () {
|
|
76
|
+
return this.#method;
|
|
77
|
+
}
|
|
78
|
+
pathWithQuery () {
|
|
79
|
+
return this.#pathWithQuery;
|
|
80
|
+
}
|
|
81
|
+
scheme () {
|
|
82
|
+
return this.#scheme;
|
|
83
|
+
}
|
|
84
|
+
authority () {
|
|
85
|
+
return this.#authority;
|
|
86
|
+
}
|
|
87
|
+
headers () {
|
|
88
|
+
return this.#headers;
|
|
89
|
+
}
|
|
90
|
+
consume () {
|
|
91
|
+
return incomingBodyCreate(this.#streamId);
|
|
92
|
+
}
|
|
93
|
+
static _create(method, pathWithQuery, scheme, authority, streamId) {
|
|
94
|
+
const incomingRequest = new IncomingRequest();
|
|
95
|
+
incomingRequest.#method = method;
|
|
96
|
+
incomingRequest.#pathWithQuery = pathWithQuery;
|
|
97
|
+
incomingRequest.#scheme = scheme;
|
|
98
|
+
incomingRequest.#authority = authority;
|
|
99
|
+
incomingRequest.#streamId = streamId;
|
|
100
|
+
return incomingRequest;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const incomingRequestCreate = IncomingRequest._create;
|
|
104
|
+
delete IncomingRequest._create;
|
|
86
105
|
|
|
87
106
|
class FutureTrailers {
|
|
88
|
-
|
|
107
|
+
_id = http.futureCnt++;
|
|
89
108
|
#value;
|
|
90
109
|
#isError;
|
|
91
|
-
subscribe
|
|
110
|
+
subscribe() {
|
|
92
111
|
// TODO
|
|
93
112
|
}
|
|
94
|
-
get
|
|
95
|
-
return { tag: this.#isError ?
|
|
113
|
+
get() {
|
|
114
|
+
return { tag: this.#isError ? "err" : "ok", val: this.#value };
|
|
96
115
|
}
|
|
97
|
-
static _create
|
|
116
|
+
static _create(value, isError) {
|
|
98
117
|
const res = new FutureTrailers();
|
|
99
118
|
res.#value = value;
|
|
100
119
|
res.#isError = isError;
|
|
@@ -105,72 +124,157 @@ export class WasiHttp {
|
|
|
105
124
|
delete FutureTrailers._create;
|
|
106
125
|
|
|
107
126
|
class OutgoingResponse {
|
|
108
|
-
|
|
109
|
-
/** @type {number} */
|
|
110
|
-
/** @type {Fields} */
|
|
127
|
+
_id = http.responseCnt++;
|
|
128
|
+
/** @type {number} */ #statusCode = 200;
|
|
129
|
+
/** @type {Fields} */ #headers;
|
|
111
130
|
|
|
112
131
|
/**
|
|
113
|
-
* @param {number} statusCode
|
|
114
|
-
* @param {Fields} headers
|
|
132
|
+
* @param {number} statusCode
|
|
133
|
+
* @param {Fields} headers
|
|
115
134
|
*/
|
|
116
|
-
constructor
|
|
117
|
-
|
|
118
|
-
this
|
|
135
|
+
constructor(headers) {
|
|
136
|
+
fieldsLock(headers);
|
|
137
|
+
this.#headers = headers;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
statusCode() {
|
|
141
|
+
return this.#statusCode;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
setStatusCode(statusCode) {
|
|
145
|
+
this.#statusCode = statusCode;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
headers() {
|
|
149
|
+
return this.#headers;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
body() {
|
|
153
|
+
let contentLengthValues = this.#headers.get("content-length");
|
|
154
|
+
if (contentLengthValues.length === 0)
|
|
155
|
+
contentLengthValues = this.#headers.get("Content-Length");
|
|
156
|
+
let contentLength;
|
|
157
|
+
if (contentLengthValues.length > 0)
|
|
158
|
+
contentLength = Number(
|
|
159
|
+
new TextDecoder().decode(contentLengthValues[0])
|
|
160
|
+
);
|
|
161
|
+
return outgoingBodyCreate(contentLength);
|
|
119
162
|
}
|
|
120
163
|
}
|
|
121
164
|
|
|
122
165
|
class ResponseOutparam {
|
|
123
|
-
|
|
124
|
-
|
|
166
|
+
#response;
|
|
167
|
+
static set(param, response) {
|
|
168
|
+
param.#response = response;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
class RequestOptions {
|
|
173
|
+
#connectTimeoutMs;
|
|
174
|
+
#firstByteTimeoutMs;
|
|
175
|
+
#betweenBytesTimeoutMs;
|
|
176
|
+
connectTimeoutMs () {
|
|
177
|
+
return this.#connectTimeoutMs;
|
|
178
|
+
}
|
|
179
|
+
setConnectTimeoutMs (duration) {
|
|
180
|
+
this.#connectTimeoutMs = duration;
|
|
181
|
+
}
|
|
182
|
+
firstByteTimeoutMs () {
|
|
183
|
+
return this.#firstByteTimeoutMs;
|
|
184
|
+
}
|
|
185
|
+
setFirstByteTimeoutMs (duration) {
|
|
186
|
+
this.#firstByteTimeoutMs = duration;
|
|
187
|
+
}
|
|
188
|
+
betweenBytesTimeoutMs () {
|
|
189
|
+
return this.#betweenBytesTimeoutMs;
|
|
190
|
+
}
|
|
191
|
+
setBetweenBytesTimeoutMs (duration) {
|
|
192
|
+
this.#betweenBytesTimeoutMs = duration;
|
|
125
193
|
}
|
|
126
194
|
}
|
|
127
195
|
|
|
128
196
|
class OutgoingRequest {
|
|
129
|
-
|
|
130
|
-
/** @type {Method} */ #method = { tag:
|
|
131
|
-
/** @type {Scheme | undefined} */ #scheme =
|
|
197
|
+
_id = http.requestCnt++;
|
|
198
|
+
/** @type {Method} */ #method = { tag: "get" };
|
|
199
|
+
/** @type {Scheme | undefined} */ #scheme = undefined;
|
|
132
200
|
/** @type {string | undefined} */ #pathWithQuery = undefined;
|
|
133
201
|
/** @type {string | undefined} */ #authority = undefined;
|
|
134
|
-
/** @type {Fields} */ #headers
|
|
135
|
-
/** @type {OutgoingBody} */ #body
|
|
136
|
-
|
|
202
|
+
/** @type {Fields} */ #headers;
|
|
203
|
+
/** @type {OutgoingBody} */ #body;
|
|
204
|
+
#bodyRequested = false;
|
|
205
|
+
constructor(headers) {
|
|
206
|
+
fieldsLock(headers);
|
|
207
|
+
this.#headers = headers;
|
|
208
|
+
let contentLengthValues = this.#headers.get("content-length");
|
|
209
|
+
if (contentLengthValues.length === 0)
|
|
210
|
+
contentLengthValues = this.#headers.get("Content-Length");
|
|
211
|
+
let contentLength;
|
|
212
|
+
if (contentLengthValues.length > 0)
|
|
213
|
+
contentLength = Number(
|
|
214
|
+
new TextDecoder().decode(contentLengthValues[0])
|
|
215
|
+
);
|
|
216
|
+
this.#body = outgoingBodyCreate(contentLength);
|
|
217
|
+
}
|
|
218
|
+
body() {
|
|
219
|
+
if (this.#bodyRequested) throw new Error("Body already requested");
|
|
220
|
+
this.#bodyRequested = true;
|
|
221
|
+
return this.#body;
|
|
222
|
+
}
|
|
223
|
+
method() {
|
|
224
|
+
return this.#method;
|
|
225
|
+
}
|
|
226
|
+
setMethod(method) {
|
|
227
|
+
if (method.tag === "other" && !method.val.match(/^[a-zA-Z-]+$/))
|
|
228
|
+
throw undefined;
|
|
137
229
|
this.#method = method;
|
|
230
|
+
}
|
|
231
|
+
pathWithQuery() {
|
|
232
|
+
return this.#pathWithQuery;
|
|
233
|
+
}
|
|
234
|
+
setPathWithQuery(pathWithQuery) {
|
|
235
|
+
if (pathWithQuery && !pathWithQuery.match(/^[a-zA-Z0-9.-_~!$&'()*+,;=:@%/]+$/))
|
|
236
|
+
throw undefined;
|
|
138
237
|
this.#pathWithQuery = pathWithQuery;
|
|
238
|
+
}
|
|
239
|
+
scheme() {
|
|
240
|
+
return this.#scheme;
|
|
241
|
+
}
|
|
242
|
+
setScheme(scheme) {
|
|
243
|
+
if (scheme?.tag === "other" && !scheme.val.match(/^[a-zA-Z]+$/))
|
|
244
|
+
throw undefined;
|
|
139
245
|
this.#scheme = scheme;
|
|
246
|
+
}
|
|
247
|
+
authority() {
|
|
248
|
+
return this.#authority;
|
|
249
|
+
}
|
|
250
|
+
setAuthority(authority) {
|
|
251
|
+
if (authority) {
|
|
252
|
+
const [host, port, ...extra] = authority.split(':');
|
|
253
|
+
const portNum = Number(port);
|
|
254
|
+
if (extra.length || port !== undefined && (portNum.toString() !== port || portNum > 9999) || !host.match(/^[a-zA-Z0-9-.]+$/))
|
|
255
|
+
throw undefined;
|
|
256
|
+
}
|
|
140
257
|
this.#authority = authority;
|
|
141
|
-
this.#headers = headers;
|
|
142
258
|
}
|
|
143
|
-
|
|
144
|
-
return this.#
|
|
259
|
+
headers() {
|
|
260
|
+
return this.#headers;
|
|
145
261
|
}
|
|
146
|
-
|
|
147
|
-
static _handle
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
};
|
|
262
|
+
[symbolDispose]() {}
|
|
263
|
+
static _handle(request, _options) {
|
|
264
|
+
// TODO: handle options timeouts
|
|
265
|
+
const scheme = schemeString(request.#scheme);
|
|
266
|
+
const url = scheme + request.#authority + (request.#pathWithQuery || '');
|
|
267
|
+
const headers = [["host", request.#authority]];
|
|
153
268
|
const decoder = new TextDecoder();
|
|
154
269
|
for (const [key, value] of request.#headers.entries()) {
|
|
155
|
-
headers[key
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
let res;
|
|
159
|
-
try {
|
|
160
|
-
res = send({
|
|
161
|
-
method: request.#method.tag,
|
|
162
|
-
uri: url,
|
|
163
|
-
headers: headers,
|
|
164
|
-
params: [],
|
|
165
|
-
body: combineChunks(outgoingBodyGetChunks(request.#body)),
|
|
166
|
-
});
|
|
167
|
-
} catch (err) {
|
|
168
|
-
return newFutureIncomingResponse(err.toString(), true);
|
|
270
|
+
headers.push([key, decoder.decode(value)]);
|
|
169
271
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
272
|
+
return futureIncomingResponseCreate(
|
|
273
|
+
request.#method.val || request.#method.tag,
|
|
274
|
+
url,
|
|
275
|
+
Object.entries(headers),
|
|
276
|
+
outgoingBodyOutputStreamId(request.#body)
|
|
277
|
+
);
|
|
174
278
|
}
|
|
175
279
|
}
|
|
176
280
|
|
|
@@ -178,121 +282,269 @@ export class WasiHttp {
|
|
|
178
282
|
delete OutgoingRequest._handle;
|
|
179
283
|
|
|
180
284
|
class OutgoingBody {
|
|
181
|
-
#
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
285
|
+
#outputStream = undefined;
|
|
286
|
+
#contentLength = undefined;
|
|
287
|
+
write() {
|
|
288
|
+
if (this.#outputStream)
|
|
289
|
+
throw new Error("output stream already created for writing");
|
|
290
|
+
this.#outputStream = outputStreamCreate(
|
|
291
|
+
HTTP,
|
|
292
|
+
ioCall(OUTPUT_STREAM_CREATE | HTTP, null, this.#contentLength)
|
|
293
|
+
);
|
|
294
|
+
this.#outputStream[symbolDispose] = () => {};
|
|
295
|
+
return this.#outputStream;
|
|
296
|
+
}
|
|
297
|
+
[symbolDispose]() {
|
|
298
|
+
this.#outputStream?.[symbolDispose]();
|
|
190
299
|
}
|
|
191
300
|
/**
|
|
192
|
-
* @param {OutgoingBody} body
|
|
301
|
+
* @param {OutgoingBody} body
|
|
193
302
|
* @param {Fields | undefined} trailers
|
|
194
303
|
*/
|
|
195
|
-
static finish
|
|
196
|
-
//
|
|
304
|
+
static finish(body, _trailers) {
|
|
305
|
+
// this will verify content length, and also verify not already finished
|
|
306
|
+
// throwing errors as appropriate
|
|
307
|
+
if (body.#outputStream)
|
|
308
|
+
ioCall(
|
|
309
|
+
HTTP_OUTPUT_STREAM_FINISH,
|
|
310
|
+
outputStreamId(body.#outputStream),
|
|
311
|
+
null
|
|
312
|
+
);
|
|
313
|
+
body.#outputStream?.[symbolDispose]();
|
|
197
314
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
315
|
+
static _outputStreamId(outgoingBody) {
|
|
316
|
+
if (outgoingBody.#outputStream)
|
|
317
|
+
return outputStreamId(outgoingBody.#outputStream);
|
|
318
|
+
}
|
|
319
|
+
static _create(contentLength) {
|
|
320
|
+
const outgoingBody = new OutgoingBody();
|
|
321
|
+
outgoingBody.#contentLength = contentLength;
|
|
322
|
+
return outgoingBody;
|
|
201
323
|
}
|
|
202
324
|
}
|
|
203
|
-
const
|
|
204
|
-
delete OutgoingBody.
|
|
325
|
+
const outgoingBodyOutputStreamId = OutgoingBody._outputStreamId;
|
|
326
|
+
delete OutgoingBody._outputStreamId;
|
|
327
|
+
|
|
328
|
+
const outgoingBodyCreate = OutgoingBody._create;
|
|
329
|
+
delete OutgoingBody._create;
|
|
205
330
|
|
|
206
331
|
class IncomingResponse {
|
|
207
|
-
|
|
208
|
-
/** @type {InputStream} */ #body;
|
|
332
|
+
_id = http.responseCnt++;
|
|
209
333
|
/** @type {Fields} */ #headers = undefined;
|
|
210
334
|
#status = 0;
|
|
211
|
-
|
|
212
|
-
status
|
|
335
|
+
/** @type {number} */ #bodyStreamId;
|
|
336
|
+
status() {
|
|
213
337
|
return this.#status;
|
|
214
338
|
}
|
|
215
|
-
headers
|
|
339
|
+
headers() {
|
|
216
340
|
return this.#headers;
|
|
217
341
|
}
|
|
218
|
-
consume
|
|
219
|
-
if (this.#
|
|
220
|
-
|
|
221
|
-
this.#
|
|
222
|
-
return
|
|
342
|
+
consume() {
|
|
343
|
+
if (this.#bodyStreamId === undefined) throw undefined;
|
|
344
|
+
const bodyStreamId = this.#bodyStreamId;
|
|
345
|
+
this.#bodyStreamId = undefined;
|
|
346
|
+
return incomingBodyCreate(bodyStreamId);
|
|
347
|
+
}
|
|
348
|
+
[symbolDispose]() {
|
|
349
|
+
if (this.#bodyStreamId) {
|
|
350
|
+
ioCall(INPUT_STREAM_DISPOSE | HTTP, this.#bodyStreamId);
|
|
351
|
+
this.#bodyStreamId = undefined;
|
|
352
|
+
}
|
|
223
353
|
}
|
|
224
|
-
static _create
|
|
354
|
+
static _create(status, headers, bodyStreamId) {
|
|
225
355
|
const res = new IncomingResponse();
|
|
226
356
|
res.#status = status;
|
|
227
357
|
res.#headers = headers;
|
|
228
|
-
res.#
|
|
229
|
-
for (const chunk of chunks) {
|
|
230
|
-
incomingBodyAddChunk(res.#body, chunk);
|
|
231
|
-
}
|
|
358
|
+
res.#bodyStreamId = bodyStreamId;
|
|
232
359
|
return res;
|
|
233
360
|
}
|
|
234
361
|
}
|
|
235
362
|
|
|
236
|
-
const
|
|
363
|
+
const incomingResponseCreate = IncomingResponse._create;
|
|
237
364
|
delete IncomingResponse._create;
|
|
238
365
|
|
|
239
366
|
class FutureIncomingResponse {
|
|
240
|
-
|
|
241
|
-
#
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
//
|
|
367
|
+
_id = http.futureCnt++;
|
|
368
|
+
#pollId;
|
|
369
|
+
subscribe() {
|
|
370
|
+
if (this.#pollId) return pollableCreate(this.#pollId);
|
|
371
|
+
// 0 poll is immediately resolving
|
|
372
|
+
return pollableCreate(0);
|
|
373
|
+
}
|
|
374
|
+
get() {
|
|
375
|
+
// already taken
|
|
376
|
+
if (!this.#pollId) return { tag: "err" };
|
|
377
|
+
const ret = ioCall(FUTURE_GET_VALUE_AND_DISPOSE | HTTP, this.#pollId);
|
|
378
|
+
if (!ret) return;
|
|
379
|
+
this.#pollId = undefined;
|
|
380
|
+
if (ret.error)
|
|
381
|
+
return { tag: "ok", val: { tag: "err", val: ret.value } };
|
|
382
|
+
const { status, headers, bodyStreamId } = ret.value;
|
|
383
|
+
const textEncoder = new TextEncoder();
|
|
384
|
+
return {
|
|
385
|
+
tag: "ok",
|
|
386
|
+
val: {
|
|
387
|
+
tag: "ok",
|
|
388
|
+
val: incomingResponseCreate(
|
|
389
|
+
status,
|
|
390
|
+
fieldsFromEntriesSafe(
|
|
391
|
+
headers.map(([key, val]) => [key, textEncoder.encode(val)])
|
|
392
|
+
),
|
|
393
|
+
bodyStreamId
|
|
394
|
+
),
|
|
395
|
+
},
|
|
396
|
+
};
|
|
245
397
|
}
|
|
246
|
-
|
|
247
|
-
|
|
398
|
+
[symbolDispose]() {
|
|
399
|
+
if (this.#pollId) ioCall(FUTURE_DISPOSE | HTTP, this.#pollId);
|
|
248
400
|
}
|
|
249
|
-
static _create
|
|
401
|
+
static _create(method, url, headers, body) {
|
|
250
402
|
const res = new FutureIncomingResponse();
|
|
251
|
-
res.#
|
|
252
|
-
|
|
403
|
+
res.#pollId = ioCall(HTTP_CREATE_REQUEST, null, {
|
|
404
|
+
method,
|
|
405
|
+
url,
|
|
406
|
+
headers,
|
|
407
|
+
body,
|
|
408
|
+
});
|
|
253
409
|
return res;
|
|
254
410
|
}
|
|
255
411
|
}
|
|
256
412
|
|
|
257
|
-
const
|
|
413
|
+
const futureIncomingResponseCreate = FutureIncomingResponse._create;
|
|
258
414
|
delete FutureIncomingResponse._create;
|
|
259
415
|
|
|
260
416
|
class Fields {
|
|
261
|
-
|
|
262
|
-
|
|
417
|
+
_id = http.fieldsCnt++;
|
|
418
|
+
#immutable = false;
|
|
419
|
+
/** @type {[string, Uint8Array[]][]} */ #entries = [];
|
|
420
|
+
/** @type {Map<string, [string, Uint8Array[]][]>} */ #table = new Map();
|
|
263
421
|
|
|
264
422
|
/**
|
|
265
|
-
* @param {[string, Uint8Array[]][]}
|
|
423
|
+
* @param {[string, Uint8Array[][]][]} entries
|
|
266
424
|
*/
|
|
267
|
-
|
|
268
|
-
|
|
425
|
+
static fromList(entries) {
|
|
426
|
+
const fields = new Fields();
|
|
427
|
+
for (const [key, value] of entries) {
|
|
428
|
+
fields.append(key, value);
|
|
429
|
+
}
|
|
430
|
+
return fields;
|
|
269
431
|
}
|
|
270
|
-
get
|
|
271
|
-
|
|
432
|
+
get(name) {
|
|
433
|
+
const tableEntries = this.#table.get(name.toLowerCase());
|
|
434
|
+
if (!tableEntries) return [];
|
|
435
|
+
return tableEntries.map(([, v]) => v);
|
|
272
436
|
}
|
|
273
|
-
set
|
|
274
|
-
this.#
|
|
437
|
+
set(name, values) {
|
|
438
|
+
if (this.#immutable) throw { tag: "immutable" };
|
|
439
|
+
try {
|
|
440
|
+
validateHeaderName(name);
|
|
441
|
+
} catch {
|
|
442
|
+
throw { tag: "invalid-syntax" };
|
|
443
|
+
}
|
|
444
|
+
for (const value of values) {
|
|
445
|
+
try {
|
|
446
|
+
validateHeaderValue(name, new TextDecoder().decode(value));
|
|
447
|
+
} catch {
|
|
448
|
+
throw { tag: "invalid-syntax" };
|
|
449
|
+
}
|
|
450
|
+
throw { tag: "invalid-syntax" };
|
|
451
|
+
}
|
|
452
|
+
const lowercased = name.toLowerCase();
|
|
453
|
+
if (_forbiddenHeaders.has(lowercased)) throw { tag: "forbidden" };
|
|
454
|
+
const tableEntries = this.#table.get(lowercased);
|
|
455
|
+
if (tableEntries)
|
|
456
|
+
this.#entries = this.#entries.filter(
|
|
457
|
+
(entry) => !tableEntries.includes(entry)
|
|
458
|
+
);
|
|
459
|
+
tableEntries.splice(0, tableEntries.length);
|
|
460
|
+
for (const value of values) {
|
|
461
|
+
const entry = [name, value];
|
|
462
|
+
this.#entries.push(entry);
|
|
463
|
+
tableEntries.push(entry);
|
|
464
|
+
}
|
|
275
465
|
}
|
|
276
|
-
delete
|
|
277
|
-
|
|
466
|
+
delete(name) {
|
|
467
|
+
if (this.#immutable) throw { tag: "immutable" };
|
|
468
|
+
const lowercased = name.toLowerCase();
|
|
469
|
+
const tableEntries = this.#table.get(lowercased);
|
|
470
|
+
if (tableEntries) {
|
|
471
|
+
this.#entries = this.#entries.filter(
|
|
472
|
+
(entry) => !tableEntries.includes(entry)
|
|
473
|
+
);
|
|
474
|
+
this.#table.delete(lowercased);
|
|
475
|
+
}
|
|
278
476
|
}
|
|
279
|
-
|
|
280
|
-
|
|
477
|
+
append(name, value) {
|
|
478
|
+
if (this.#immutable) throw { tag: "immutable" };
|
|
479
|
+
try {
|
|
480
|
+
validateHeaderName(name);
|
|
481
|
+
} catch {
|
|
482
|
+
throw { tag: "invalid-syntax" };
|
|
483
|
+
}
|
|
484
|
+
try {
|
|
485
|
+
validateHeaderValue(name, new TextDecoder().decode(value));
|
|
486
|
+
} catch (e) {
|
|
487
|
+
throw { tag: "invalid-syntax" };
|
|
488
|
+
}
|
|
489
|
+
const lowercased = name.toLowerCase();
|
|
490
|
+
if (_forbiddenHeaders.has(lowercased)) throw { tag: "forbidden" };
|
|
491
|
+
const entry = [name, value];
|
|
492
|
+
this.#entries.push(entry);
|
|
493
|
+
const tableEntries = this.#table.get(lowercased);
|
|
494
|
+
if (tableEntries) {
|
|
495
|
+
tableEntries.push(entry);
|
|
496
|
+
} else {
|
|
497
|
+
this.#table.set(lowercased, [entry]);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
entries() {
|
|
501
|
+
return this.#entries;
|
|
502
|
+
}
|
|
503
|
+
clone() {
|
|
504
|
+
return fieldsFromEntriesSafe(this.#entries);
|
|
281
505
|
}
|
|
282
|
-
|
|
283
|
-
|
|
506
|
+
static _lock(fields) {
|
|
507
|
+
fields.#immutable = true;
|
|
508
|
+
}
|
|
509
|
+
// assumes entries are already validated
|
|
510
|
+
static _fromEntriesChecked(entries) {
|
|
511
|
+
const fields = new Fields();
|
|
512
|
+
fields.#entries = entries;
|
|
513
|
+
for (const entry of entries) {
|
|
514
|
+
const lowercase = entry[0].toLowerCase();
|
|
515
|
+
const existing = fields.#table.get(lowercase);
|
|
516
|
+
if (existing) {
|
|
517
|
+
existing.push(entry);
|
|
518
|
+
} else {
|
|
519
|
+
fields.#table.set(lowercase, [entry]);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return fields;
|
|
284
523
|
}
|
|
285
524
|
}
|
|
525
|
+
const fieldsLock = Fields._lock;
|
|
526
|
+
delete Fields._lock;
|
|
527
|
+
const fieldsFromEntriesSafe = Fields._fromEntriesChecked;
|
|
528
|
+
delete Fields._fromEntriesChecked;
|
|
286
529
|
|
|
287
530
|
this.outgoingHandler = {
|
|
288
531
|
/**
|
|
289
532
|
* @param {OutgoingRequest} request
|
|
290
|
-
* @param {RequestOptions | undefined}
|
|
533
|
+
* @param {RequestOptions | undefined} options
|
|
291
534
|
* @returns {FutureIncomingResponse}
|
|
292
535
|
*/
|
|
293
|
-
handle: outgoingRequestHandle
|
|
536
|
+
handle: outgoingRequestHandle,
|
|
294
537
|
};
|
|
295
538
|
|
|
539
|
+
this._incomingRequestCreate = incomingRequestCreate;
|
|
540
|
+
|
|
541
|
+
function httpErrorCode(err) {
|
|
542
|
+
return {
|
|
543
|
+
tag: "internal-error",
|
|
544
|
+
val: err.message,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
|
|
296
548
|
this.types = {
|
|
297
549
|
Fields,
|
|
298
550
|
FutureIncomingResponse,
|
|
@@ -303,9 +555,24 @@ export class WasiHttp {
|
|
|
303
555
|
OutgoingBody,
|
|
304
556
|
OutgoingRequest,
|
|
305
557
|
OutgoingResponse,
|
|
306
|
-
ResponseOutparam
|
|
558
|
+
ResponseOutparam,
|
|
559
|
+
RequestOptions,
|
|
560
|
+
httpErrorCode,
|
|
307
561
|
};
|
|
308
562
|
}
|
|
309
563
|
}
|
|
310
564
|
|
|
565
|
+
function schemeString(scheme) {
|
|
566
|
+
if (!scheme)
|
|
567
|
+
return 'https:';
|
|
568
|
+
switch (scheme.tag) {
|
|
569
|
+
case "HTTP":
|
|
570
|
+
return "http:";
|
|
571
|
+
case "HTTPS":
|
|
572
|
+
return "https:";
|
|
573
|
+
case "other":
|
|
574
|
+
return scheme.val.toLowerCase() + ":";
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
311
578
|
export const { outgoingHandler, types } = new WasiHttp();
|