@bytecodealliance/preview3-shim 0.1.1 → 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/dist/nodejs/cli.d.ts +51 -0
- package/dist/nodejs/cli.js +106 -0
- package/dist/nodejs/clocks.d.ts +55 -0
- package/dist/nodejs/clocks.js +83 -0
- package/dist/nodejs/filesystem/descriptor.d.ts +443 -0
- package/dist/nodejs/filesystem/descriptor.js +1010 -0
- package/dist/nodejs/filesystem/error.d.ts +61 -0
- package/dist/nodejs/filesystem/error.js +86 -0
- package/dist/nodejs/filesystem/utils.d.ts +8 -0
- package/dist/nodejs/filesystem/utils.js +25 -0
- package/dist/nodejs/filesystem.js +2 -0
- package/dist/nodejs/finalization.d.ts +2 -0
- package/{lib → dist}/nodejs/finalization.js +15 -21
- package/dist/nodejs/future.d.ts +98 -0
- package/dist/nodejs/future.js +176 -0
- package/dist/nodejs/http/client.d.ts +22 -0
- package/dist/nodejs/http/client.js +138 -0
- package/dist/nodejs/http/error.d.ts +18 -0
- package/dist/nodejs/http/error.js +62 -0
- package/dist/nodejs/http/fields.d.ts +144 -0
- package/dist/nodejs/http/fields.js +327 -0
- package/dist/nodejs/http/request.d.ts +256 -0
- package/dist/nodejs/http/request.js +606 -0
- package/dist/nodejs/http/response.d.ts +82 -0
- package/dist/nodejs/http/response.js +202 -0
- package/dist/nodejs/http/server.d.ts +13 -0
- package/dist/nodejs/http/server.js +127 -0
- package/dist/nodejs/http.d.ts +19 -0
- package/{lib → dist}/nodejs/http.js +13 -18
- package/dist/nodejs/index.js +8 -0
- package/dist/nodejs/random.d.ts +5 -0
- package/{lib → dist}/nodejs/random.js +2 -8
- package/dist/nodejs/sockets/address.d.ts +78 -0
- package/dist/nodejs/sockets/address.js +120 -0
- package/dist/nodejs/sockets/error.d.ts +83 -0
- package/dist/nodejs/sockets/error.js +120 -0
- package/dist/nodejs/sockets/ip-name-lookup.d.ts +18 -0
- package/dist/nodejs/sockets/ip-name-lookup.js +84 -0
- package/dist/nodejs/sockets/tcp.d.ts +333 -0
- package/dist/nodejs/sockets/tcp.js +723 -0
- package/dist/nodejs/sockets/udp.d.ts +224 -0
- package/dist/nodejs/sockets/udp.js +475 -0
- package/dist/nodejs/sockets.d.ts +11 -0
- package/{lib → dist}/nodejs/sockets.js +2 -5
- package/dist/nodejs/stream.d.ts +161 -0
- package/dist/nodejs/stream.js +388 -0
- package/dist/nodejs/workers/cli-worker.d.ts +1 -0
- package/dist/nodejs/workers/cli-worker.js +41 -0
- package/dist/nodejs/workers/filesystem-worker.d.ts +1 -0
- package/dist/nodejs/workers/filesystem-worker.js +102 -0
- package/dist/nodejs/workers/http-worker.d.ts +1 -0
- package/dist/nodejs/workers/http-worker.js +355 -0
- package/dist/nodejs/workers/resource-worker.d.ts +13 -0
- package/dist/nodejs/workers/resource-worker.js +152 -0
- package/dist/nodejs/workers/tcp-worker.d.ts +2 -0
- package/dist/nodejs/workers/tcp-worker.js +318 -0
- package/dist/nodejs/workers/udp-worker.d.ts +1 -0
- package/dist/nodejs/workers/udp-worker.js +156 -0
- package/package.json +20 -12
- package/types/interfaces/wasi-cli-environment.d.ts +3 -3
- package/types/interfaces/wasi-cli-exit.d.ts +12 -1
- package/types/interfaces/wasi-cli-run.d.ts +1 -1
- package/types/interfaces/wasi-cli-stderr.d.ts +3 -3
- package/types/interfaces/wasi-cli-stdin.d.ts +5 -5
- package/types/interfaces/wasi-cli-stdout.d.ts +3 -3
- package/types/interfaces/wasi-cli-terminal-input.d.ts +1 -1
- package/types/interfaces/wasi-cli-terminal-output.d.ts +1 -1
- package/types/interfaces/wasi-cli-terminal-stderr.d.ts +1 -1
- package/types/interfaces/wasi-cli-terminal-stdin.d.ts +1 -1
- package/types/interfaces/wasi-cli-terminal-stdout.d.ts +1 -1
- package/types/interfaces/wasi-cli-types.d.ts +5 -5
- package/types/interfaces/wasi-clocks-monotonic-clock.d.ts +3 -3
- package/types/interfaces/wasi-clocks-system-clock.d.ts +6 -6
- package/types/interfaces/wasi-clocks-types.d.ts +1 -1
- package/types/interfaces/wasi-filesystem-preopens.d.ts +1 -1
- package/types/interfaces/wasi-filesystem-types.d.ts +74 -74
- package/types/interfaces/wasi-http-client.d.ts +1 -1
- package/types/interfaces/wasi-http-handler.d.ts +1 -1
- package/types/interfaces/wasi-http-types.d.ts +34 -34
- package/types/interfaces/wasi-random-insecure-seed.d.ts +5 -5
- package/types/interfaces/wasi-random-insecure.d.ts +5 -5
- package/types/interfaces/wasi-random-random.d.ts +5 -5
- package/types/interfaces/wasi-sockets-ip-name-lookup.d.ts +11 -11
- package/types/interfaces/wasi-sockets-types.d.ts +121 -121
- package/types/wasi-cli-command.d.ts +23 -23
- package/types/wasi-http-service.d.ts +14 -14
- package/lib/nodejs/cli.js +0 -127
- package/lib/nodejs/clocks.js +0 -100
- package/lib/nodejs/filesystem/descriptor.js +0 -1093
- package/lib/nodejs/filesystem/error.js +0 -92
- package/lib/nodejs/filesystem/utils.js +0 -19
- package/lib/nodejs/future.js +0 -190
- package/lib/nodejs/http/client.js +0 -161
- package/lib/nodejs/http/error.js +0 -71
- package/lib/nodejs/http/fields.js +0 -373
- package/lib/nodejs/http/request.js +0 -691
- package/lib/nodejs/http/response.js +0 -227
- package/lib/nodejs/http/server.js +0 -157
- package/lib/nodejs/sockets/address.js +0 -146
- package/lib/nodejs/sockets/error.js +0 -123
- package/lib/nodejs/sockets/ip-name-lookup.js +0 -93
- package/lib/nodejs/sockets/tcp.js +0 -801
- package/lib/nodejs/sockets/udp.js +0 -516
- package/lib/nodejs/stream.js +0 -437
- package/lib/nodejs/workers/cli-worker.js +0 -49
- package/lib/nodejs/workers/filesystem-worker.js +0 -119
- package/lib/nodejs/workers/http-worker.js +0 -417
- package/lib/nodejs/workers/resource-worker.js +0 -195
- package/lib/nodejs/workers/tcp-worker.js +0 -370
- package/lib/nodejs/workers/udp-worker.js +0 -194
- /package/{lib/nodejs/filesystem.js → dist/nodejs/filesystem.d.ts} +0 -0
- /package/{lib/nodejs/index.js → dist/nodejs/index.d.ts} +0 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { FutureReader, future } from "../future.js";
|
|
2
|
+
import { StreamReader, readableByteStreamFromReader } from "../stream.js";
|
|
3
|
+
import { HttpError } from "./error.js";
|
|
4
|
+
import { Fields, _fieldsLock } from "./fields.js";
|
|
5
|
+
const symbolDispose = Symbol.dispose || Symbol.for("dispose");
|
|
6
|
+
let RESPONSE_CREATE_TOKEN = null;
|
|
7
|
+
function token() {
|
|
8
|
+
return (RESPONSE_CREATE_TOKEN ??= Symbol("ResponseCreateToken"));
|
|
9
|
+
}
|
|
10
|
+
export class Response {
|
|
11
|
+
#statusCode = 200;
|
|
12
|
+
#headers = null;
|
|
13
|
+
#contents = null;
|
|
14
|
+
#trailersFuture = null;
|
|
15
|
+
#responseFuture = null;
|
|
16
|
+
#contentsDispose = null;
|
|
17
|
+
#bodyOpen = false;
|
|
18
|
+
#bodyEnded = false;
|
|
19
|
+
/**
|
|
20
|
+
* @private
|
|
21
|
+
* Use Response.new(...) to create an instance
|
|
22
|
+
*/
|
|
23
|
+
constructor(t) {
|
|
24
|
+
if (t !== token()) {
|
|
25
|
+
throw new Error("Use Response.new(...) to create a Response");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Construct a new Response with default status-code 200.
|
|
30
|
+
*
|
|
31
|
+
* WIT:
|
|
32
|
+
* ```
|
|
33
|
+
* new: static func(
|
|
34
|
+
* headers: headers,
|
|
35
|
+
* contents: option<stream<u8>>,
|
|
36
|
+
* trailers: future<result<option<trailers>, error-code>>
|
|
37
|
+
* ) -> tuple<response, future<result<_, error-code>>>;
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @param {Fields} headers immutable headers resource
|
|
41
|
+
* @param {?StreamReader|object} contents optional body stream
|
|
42
|
+
* @param {FutureReader|Promise} trailers future for trailers
|
|
43
|
+
* @returns {{ res: Response, future: FutureReader }}
|
|
44
|
+
* @throws {HttpError} with payload.tag 'invalid-argument' for invalid arguments
|
|
45
|
+
*/
|
|
46
|
+
static new(headers, contents, trailers) {
|
|
47
|
+
if (!(headers instanceof Fields)) {
|
|
48
|
+
throw new HttpError("invalid-argument", "headers must be Fields");
|
|
49
|
+
}
|
|
50
|
+
if (!(trailers instanceof FutureReader) && (!trailers || typeof trailers.then !== "function")) {
|
|
51
|
+
throw new HttpError("invalid-argument", "trailers must be FutureReader or Promise");
|
|
52
|
+
}
|
|
53
|
+
let dispose = null;
|
|
54
|
+
if (contents != null && !(contents instanceof StreamReader)) {
|
|
55
|
+
try {
|
|
56
|
+
dispose = contents[symbolDispose]?.bind(contents);
|
|
57
|
+
const inner = readableByteStreamFromReader(contents, { name: "contents" });
|
|
58
|
+
contents = new StreamReader(inner);
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
throw new HttpError("invalid-argument", err.message);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Generated P3 futures are lazy thenables so we want to observe them now so early error paths don't hang.
|
|
65
|
+
if (!(trailers instanceof FutureReader)) {
|
|
66
|
+
const promise = Promise.resolve(trailers);
|
|
67
|
+
trailers = new FutureReader(promise);
|
|
68
|
+
}
|
|
69
|
+
const response = new Response(token());
|
|
70
|
+
response.#headers = _fieldsLock(headers);
|
|
71
|
+
response.#contents = contents;
|
|
72
|
+
response.#contentsDispose = dispose;
|
|
73
|
+
response.#trailersFuture = trailers;
|
|
74
|
+
const { tx, rx } = future();
|
|
75
|
+
response.#responseFuture = tx;
|
|
76
|
+
return [response, rx];
|
|
77
|
+
}
|
|
78
|
+
[symbolDispose]() {
|
|
79
|
+
if (this.#contents && !this.#bodyOpen && !this.#bodyEnded) {
|
|
80
|
+
this.#contentsDispose?.();
|
|
81
|
+
this.#closeBody();
|
|
82
|
+
}
|
|
83
|
+
this.#contents = null;
|
|
84
|
+
this.#contentsDispose = null;
|
|
85
|
+
this.#bodyOpen = false;
|
|
86
|
+
this.#bodyEnded = true;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get the HTTP Status Code for the Response.
|
|
90
|
+
*
|
|
91
|
+
* WIT:
|
|
92
|
+
* ```
|
|
93
|
+
* status-code: func() -> status-code;
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* @returns {number}
|
|
97
|
+
*/
|
|
98
|
+
getStatusCode() {
|
|
99
|
+
return this.#statusCode;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Set the HTTP Status Code for the Response.
|
|
103
|
+
*
|
|
104
|
+
* WIT:
|
|
105
|
+
* ```
|
|
106
|
+
* set-status-code: func(status-code: status-code) -> result;
|
|
107
|
+
* ```
|
|
108
|
+
*
|
|
109
|
+
* @param {number} code
|
|
110
|
+
* @throws {HttpError} with payload.tag 'invalid-argument'
|
|
111
|
+
* if code is not an integer in [100,599]
|
|
112
|
+
*/
|
|
113
|
+
setStatusCode(code) {
|
|
114
|
+
if (typeof code !== "number" || !Number.isInteger(code) || code < 100 || code > 599) {
|
|
115
|
+
throw new HttpError("invalid-argument", `Invalid status code: ${code}`);
|
|
116
|
+
}
|
|
117
|
+
this.#statusCode = code;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get the headers associated with the Response.
|
|
121
|
+
*
|
|
122
|
+
* WIT:
|
|
123
|
+
* ```
|
|
124
|
+
* headers: func() -> headers;
|
|
125
|
+
* ```
|
|
126
|
+
*
|
|
127
|
+
* @returns {Fields}
|
|
128
|
+
*/
|
|
129
|
+
getHeaders() {
|
|
130
|
+
return this.#headers;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get body stream and trailers future, consuming the resource.
|
|
134
|
+
*
|
|
135
|
+
* WIT:
|
|
136
|
+
* ```
|
|
137
|
+
* consume-body: static func(this: response, res: future<result<_, error-code>>)
|
|
138
|
+
* -> tuple<stream<u8>, future<result<option<trailers>, error-code>>>;
|
|
139
|
+
* ```
|
|
140
|
+
*
|
|
141
|
+
* @param {Response} response - The response to consume.
|
|
142
|
+
* @param {FutureReader} res - A future for communicating errors.
|
|
143
|
+
* @returns {[StreamReader, FutureReader]} A tuple of [body stream, trailers future].
|
|
144
|
+
* @throws {HttpError} with payload.tag 'invalid-state' if body has already been opened or consumed.
|
|
145
|
+
*/
|
|
146
|
+
static consumeBody(response, res) {
|
|
147
|
+
if (res && typeof res.then === "function") {
|
|
148
|
+
// TODO: Use res to abort/close the body transport on errors.
|
|
149
|
+
void Promise.resolve(res).catch(() => { });
|
|
150
|
+
}
|
|
151
|
+
if (response.#bodyOpen) {
|
|
152
|
+
throw new HttpError("invalid-state", "body already opened and not yet closed");
|
|
153
|
+
}
|
|
154
|
+
if (response.#bodyEnded) {
|
|
155
|
+
throw new HttpError("invalid-state", "body has already been consumed");
|
|
156
|
+
}
|
|
157
|
+
if (!response.#contents) {
|
|
158
|
+
response.#bodyEnded = true;
|
|
159
|
+
return [response.#contents, response.#trailersFuture];
|
|
160
|
+
}
|
|
161
|
+
const reader = response.#contents;
|
|
162
|
+
response.#bodyOpen = true;
|
|
163
|
+
const readFn = reader.read.bind(reader);
|
|
164
|
+
reader.read = async (...args) => {
|
|
165
|
+
const chunk = await readFn(...args);
|
|
166
|
+
if (chunk === null) {
|
|
167
|
+
response.#bodyEnded = true;
|
|
168
|
+
response.#bodyOpen = false;
|
|
169
|
+
}
|
|
170
|
+
return chunk;
|
|
171
|
+
};
|
|
172
|
+
const closedFn = reader.close.bind(reader);
|
|
173
|
+
reader.close = () => {
|
|
174
|
+
response.#closeBody(closedFn);
|
|
175
|
+
};
|
|
176
|
+
const cancelFn = reader.cancel.bind(reader);
|
|
177
|
+
reader.cancel = async (...args) => {
|
|
178
|
+
await response.#closeBody(() => cancelFn(...args));
|
|
179
|
+
};
|
|
180
|
+
return [response.#contents, response.#trailersFuture];
|
|
181
|
+
}
|
|
182
|
+
// Internal: call to complete the response transmission
|
|
183
|
+
_resolve(result) {
|
|
184
|
+
if (this.#responseFuture) {
|
|
185
|
+
this.#responseFuture.write(result);
|
|
186
|
+
this.#responseFuture = null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
#closeBody(close = this.#contents?.close.bind(this.#contents)) {
|
|
190
|
+
const closeTrailers = !this.#bodyEnded;
|
|
191
|
+
try {
|
|
192
|
+
return close?.();
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
if (closeTrailers) {
|
|
196
|
+
this.#trailersFuture.close();
|
|
197
|
+
}
|
|
198
|
+
this.#bodyEnded = true;
|
|
199
|
+
this.#bodyOpen = false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
/**
|
|
3
|
+
* HttpServer uses a background worker thread to handle raw HTTP connections,
|
|
4
|
+
* while the main thread invokes the user-provided handler and sends back the
|
|
5
|
+
* response.
|
|
6
|
+
*/
|
|
7
|
+
export declare class HttpServer extends EventEmitter {
|
|
8
|
+
#private;
|
|
9
|
+
constructor(handler: any);
|
|
10
|
+
listen(port: any, host: any): Promise<void>;
|
|
11
|
+
stop(): Promise<void>;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { MessageChannel } from "node:worker_threads";
|
|
3
|
+
import { ResourceWorker } from "../workers/resource-worker.js";
|
|
4
|
+
import { StreamReader, readableByteStreamFromReader } from "../stream.js";
|
|
5
|
+
import { FutureReader, future } from "../future.js";
|
|
6
|
+
import { Request } from "./request.js";
|
|
7
|
+
import { Response } from "./response.js";
|
|
8
|
+
import { HttpError } from "./error.js";
|
|
9
|
+
import { _fieldsFromEntriesChecked, _readTrailersForTransport, _trailerResultFromEntries, } from "./fields.js";
|
|
10
|
+
let WORKER = null;
|
|
11
|
+
function worker() {
|
|
12
|
+
return (WORKER ??= new ResourceWorker(new URL("../workers/http-worker.js", import.meta.url)));
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* HttpServer uses a background worker thread to handle raw HTTP connections,
|
|
16
|
+
* while the main thread invokes the user-provided handler and sends back the
|
|
17
|
+
* response.
|
|
18
|
+
*/
|
|
19
|
+
export class HttpServer extends EventEmitter {
|
|
20
|
+
#serverId = null;
|
|
21
|
+
#handler = null;
|
|
22
|
+
#shutdown = null;
|
|
23
|
+
constructor(handler) {
|
|
24
|
+
super();
|
|
25
|
+
if (typeof handler?.handle !== "function") {
|
|
26
|
+
throw new HttpError("invalid-argument", "Not a valid HTTP server component to execute.");
|
|
27
|
+
}
|
|
28
|
+
this.#handler = handler;
|
|
29
|
+
}
|
|
30
|
+
async listen(port, host) {
|
|
31
|
+
this.#serverId = await worker().run({
|
|
32
|
+
op: "server-start",
|
|
33
|
+
port,
|
|
34
|
+
host,
|
|
35
|
+
});
|
|
36
|
+
this.#shutdown = this.#serveLoop();
|
|
37
|
+
}
|
|
38
|
+
async stop() {
|
|
39
|
+
if (!this.#serverId) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
await worker().run({
|
|
43
|
+
op: "server-stop",
|
|
44
|
+
serverId: this.#serverId,
|
|
45
|
+
});
|
|
46
|
+
await this.#shutdown;
|
|
47
|
+
}
|
|
48
|
+
async close() {
|
|
49
|
+
if (!this.#serverId) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
await this.stop();
|
|
53
|
+
await worker().run({
|
|
54
|
+
op: "server-close",
|
|
55
|
+
serverId: this.#serverId,
|
|
56
|
+
});
|
|
57
|
+
this.#serverId = null;
|
|
58
|
+
}
|
|
59
|
+
async #serveLoop() {
|
|
60
|
+
let next;
|
|
61
|
+
const nextRequest = async () => await worker().run({
|
|
62
|
+
op: "server-next",
|
|
63
|
+
serverId: this.#serverId,
|
|
64
|
+
});
|
|
65
|
+
while ((next = await nextRequest()) !== null) {
|
|
66
|
+
try {
|
|
67
|
+
const { requestId } = next;
|
|
68
|
+
const req = requestFromParts(next);
|
|
69
|
+
const outcome = await this.#handler.handle(req);
|
|
70
|
+
if (outcome.tag === "ok") {
|
|
71
|
+
const res = outcome.val;
|
|
72
|
+
const { rx: resRx } = future();
|
|
73
|
+
const [body, trailers] = Response.consumeBody(res, resRx);
|
|
74
|
+
const { port1: tx, port2: rx } = new MessageChannel();
|
|
75
|
+
const stream = readableByteStreamFromReader(body, { name: "response body" });
|
|
76
|
+
_readTrailersForTransport(trailers)
|
|
77
|
+
.then((val) => tx.postMessage({ val }))
|
|
78
|
+
.catch((err) => tx.postMessage({ err: HttpError.from(err).payload }))
|
|
79
|
+
.finally(() => tx.close());
|
|
80
|
+
await worker().run({
|
|
81
|
+
op: "server-response",
|
|
82
|
+
serverId: this.#serverId,
|
|
83
|
+
requestId,
|
|
84
|
+
statusCode: res.getStatusCode(),
|
|
85
|
+
headers: res.getHeaders().copyAll(),
|
|
86
|
+
trailers: rx,
|
|
87
|
+
stream,
|
|
88
|
+
}, [stream, rx]);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
await worker().run({
|
|
92
|
+
op: "server-response",
|
|
93
|
+
serverId: this.#serverId,
|
|
94
|
+
requestId,
|
|
95
|
+
statusCode: 500,
|
|
96
|
+
headers: [],
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
this.emit("error", err);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const requestFromParts = (parts) => {
|
|
107
|
+
const { headers, body, trailers, method, url } = parts;
|
|
108
|
+
// Create a promise that resolves when the worker sends a message with trailers
|
|
109
|
+
const promise = new Promise((resolve, reject) => {
|
|
110
|
+
trailers.once("message", ({ val, err }) => {
|
|
111
|
+
trailers.close();
|
|
112
|
+
if (err) {
|
|
113
|
+
reject(err);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
resolve(val);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
const future = new FutureReader(promise.then(_trailerResultFromEntries));
|
|
121
|
+
const contents = new StreamReader(body);
|
|
122
|
+
const fields = _fieldsFromEntriesChecked(headers);
|
|
123
|
+
const [req] = Request.new(fields, contents, future, null);
|
|
124
|
+
req.setMethod(method);
|
|
125
|
+
req.setPathWithQuery(url);
|
|
126
|
+
return req;
|
|
127
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Fields } from "./http/fields.js";
|
|
2
|
+
import { Request, RequestOptions } from "./http/request.js";
|
|
3
|
+
import { Response } from "./http/response.js";
|
|
4
|
+
export * from "./http/client.js";
|
|
5
|
+
export * from "./http/error.js";
|
|
6
|
+
export * from "./http/fields.js";
|
|
7
|
+
export * from "./http/request.js";
|
|
8
|
+
export * from "./http/response.js";
|
|
9
|
+
export * from "./http/server.js";
|
|
10
|
+
export declare const handler: {
|
|
11
|
+
handle(request: any): any;
|
|
12
|
+
};
|
|
13
|
+
export declare function _setHandler(nextHandler: any): any;
|
|
14
|
+
export declare const types: {
|
|
15
|
+
Fields: typeof Fields;
|
|
16
|
+
Request: typeof Request;
|
|
17
|
+
RequestOptions: typeof RequestOptions;
|
|
18
|
+
Response: typeof Response;
|
|
19
|
+
};
|
|
@@ -1,34 +1,29 @@
|
|
|
1
1
|
import { Fields } from "./http/fields.js";
|
|
2
2
|
import { Request, RequestOptions } from "./http/request.js";
|
|
3
3
|
import { Response } from "./http/response.js";
|
|
4
|
-
|
|
5
4
|
export * from "./http/client.js";
|
|
6
5
|
export * from "./http/error.js";
|
|
7
6
|
export * from "./http/fields.js";
|
|
8
7
|
export * from "./http/request.js";
|
|
9
8
|
export * from "./http/response.js";
|
|
10
9
|
export * from "./http/server.js";
|
|
11
|
-
|
|
12
10
|
let currentHandler = null;
|
|
13
|
-
|
|
14
11
|
export const handler = {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
handle(request) {
|
|
13
|
+
if (!currentHandler) {
|
|
14
|
+
throw new Error("wasi:http/handler import is not configured");
|
|
15
|
+
}
|
|
16
|
+
return currentHandler.handle(request);
|
|
17
|
+
},
|
|
21
18
|
};
|
|
22
|
-
|
|
23
19
|
export function _setHandler(nextHandler) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
const previous = currentHandler;
|
|
21
|
+
currentHandler = nextHandler;
|
|
22
|
+
return previous;
|
|
27
23
|
}
|
|
28
|
-
|
|
29
24
|
export const types = {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
Fields,
|
|
26
|
+
Request,
|
|
27
|
+
RequestOptions,
|
|
28
|
+
Response,
|
|
34
29
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * as cli from "./cli.js";
|
|
2
|
+
export * as clocks from "./clocks.js";
|
|
3
|
+
export * as filesystem from "./filesystem.js";
|
|
4
|
+
export * as future from "./future.js";
|
|
5
|
+
export * as http from "./http.js";
|
|
6
|
+
export * as random from "./random.js";
|
|
7
|
+
export * as sockets from "./sockets.js";
|
|
8
|
+
export * as stream from "./stream.js";
|
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
insecure,
|
|
3
|
-
insecureSeed as insecureSeedV2,
|
|
4
|
-
random,
|
|
5
|
-
} from "@bytecodealliance/preview2-shim/random";
|
|
6
|
-
|
|
1
|
+
import { insecure, insecureSeed as insecureSeedV2, random, } from "@bytecodealliance/preview2-shim/random";
|
|
7
2
|
export { insecure, random };
|
|
8
|
-
|
|
9
3
|
// `wasi:random/insecure-seed` was renamed between p2 (`insecure-seed`)
|
|
10
4
|
// and p3 (`get-insecure-seed`). Rename the member to match the p3 WIT
|
|
11
5
|
// while keeping the original implementation.
|
|
12
6
|
export const insecureSeed = {
|
|
13
|
-
|
|
7
|
+
getInsecureSeed: insecureSeedV2.insecureSeed,
|
|
14
8
|
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export declare const IP_ADDRESS_FAMILY: {
|
|
2
|
+
IPV4: string;
|
|
3
|
+
IPV6: string;
|
|
4
|
+
};
|
|
5
|
+
export declare const tupleToIpv6: (segments: any) => any;
|
|
6
|
+
export declare const tupleToIpv4: (segments: any) => any;
|
|
7
|
+
export declare const ipv6ToTuple: (ipv6: any) => number[];
|
|
8
|
+
export declare const ipv4ToTuple: (ipv4: any) => any;
|
|
9
|
+
export declare const isMulticastIpAddress: ({ tag, val: { address } }: {
|
|
10
|
+
tag: any;
|
|
11
|
+
val: {
|
|
12
|
+
address: any;
|
|
13
|
+
};
|
|
14
|
+
}) => boolean;
|
|
15
|
+
export declare const isIpv4MappedAddress: ({ tag, val: { address } }: {
|
|
16
|
+
tag: any;
|
|
17
|
+
val: {
|
|
18
|
+
address: any;
|
|
19
|
+
};
|
|
20
|
+
}) => boolean;
|
|
21
|
+
export declare const isWildcardIpAddress: ({ val: { address } }: {
|
|
22
|
+
val: {
|
|
23
|
+
address: any;
|
|
24
|
+
};
|
|
25
|
+
}) => any;
|
|
26
|
+
export declare const isBroadcastIpAddress: ({ tag, val: { address } }: {
|
|
27
|
+
tag: any;
|
|
28
|
+
val: {
|
|
29
|
+
address: any;
|
|
30
|
+
};
|
|
31
|
+
}) => any;
|
|
32
|
+
export declare const isUnicastIpAddress: (a: any) => boolean;
|
|
33
|
+
export declare const isLoopbackIpAddress: ({ tag, val: { address } }: {
|
|
34
|
+
tag: any;
|
|
35
|
+
val: {
|
|
36
|
+
address: any;
|
|
37
|
+
};
|
|
38
|
+
}) => boolean;
|
|
39
|
+
export declare const ipAddressConflict: (a: any, b: any) => any;
|
|
40
|
+
/**
|
|
41
|
+
* Serialize a socket‐address to text.
|
|
42
|
+
* @param {{tag:string,val:{address:number[]}}} ipAddr
|
|
43
|
+
* @returns {string|null}
|
|
44
|
+
*/
|
|
45
|
+
export declare const serializeIpAddress: ({ tag, val: { address } }: {
|
|
46
|
+
tag: any;
|
|
47
|
+
val: {
|
|
48
|
+
address: any;
|
|
49
|
+
};
|
|
50
|
+
}) => any;
|
|
51
|
+
/**
|
|
52
|
+
* Create a socket‐address object.
|
|
53
|
+
* @param {'ipv4'|'ipv6'} family
|
|
54
|
+
* @param {string} host IP text
|
|
55
|
+
* @param {number|bigint} port
|
|
56
|
+
* @returns {{tag:string,val:Object}}
|
|
57
|
+
*/
|
|
58
|
+
export declare const makeIpAddress: (family: any, host: any, port: any) => {
|
|
59
|
+
tag: string;
|
|
60
|
+
val: {
|
|
61
|
+
address: any;
|
|
62
|
+
port: any;
|
|
63
|
+
};
|
|
64
|
+
} | {
|
|
65
|
+
tag: string;
|
|
66
|
+
val: {
|
|
67
|
+
flowInfo: number;
|
|
68
|
+
scopeId: number;
|
|
69
|
+
address: any;
|
|
70
|
+
port: any;
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Determine whether an IP address can be used as a local bind address.
|
|
75
|
+
* @param {{tag:'ipv4'|'ipv6',val:{address:number[]}}} ipAddress
|
|
76
|
+
* @returns {boolean}
|
|
77
|
+
*/
|
|
78
|
+
export declare const isBindableIpAddress: (ipAddress: any) => boolean;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Adapted from preview2-shim/src/io/worker-sockets.ts
|
|
2
|
+
import { networkInterfaces } from "node:os";
|
|
3
|
+
// TODO(tandr): switch to generated types
|
|
4
|
+
export const IP_ADDRESS_FAMILY = {
|
|
5
|
+
IPV4: "ipv4",
|
|
6
|
+
IPV6: "ipv6",
|
|
7
|
+
};
|
|
8
|
+
export const tupleToIpv6 = (segments) => {
|
|
9
|
+
if (segments.length !== 8) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
return segments.map((segment) => segment.toString(16)).join(":");
|
|
13
|
+
};
|
|
14
|
+
export const tupleToIpv4 = (segments) => {
|
|
15
|
+
if (segments.length !== 4) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return segments.map((segment) => segment.toString(10)).join(".");
|
|
19
|
+
};
|
|
20
|
+
export const ipv6ToTuple = (ipv6) => {
|
|
21
|
+
const [lhs, rhs = ""] = ipv6.includes("::") ? ipv6.split("::") : [ipv6];
|
|
22
|
+
const lhsParts = lhs === "" ? [] : lhs.split(":");
|
|
23
|
+
const rhsParts = rhs === "" ? [] : rhs.split(":");
|
|
24
|
+
return [...lhsParts, ...Array(8 - lhsParts.length - rhsParts.length).fill(0), ...rhsParts].map((segment) => {
|
|
25
|
+
const val = parseInt(segment, 16);
|
|
26
|
+
if (val < 0 || val > 0xffff || Number.isNaN(val)) {
|
|
27
|
+
throw new Error(`Invalid IPv6 segment: ${segment}`);
|
|
28
|
+
}
|
|
29
|
+
return val;
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
export const ipv4ToTuple = (ipv4) => {
|
|
33
|
+
return ipv4.split(".").map((segment) => {
|
|
34
|
+
const val = parseInt(segment, 10);
|
|
35
|
+
if (val < 0 || val > 255 || Number.isNaN(val)) {
|
|
36
|
+
throw new Error(`Invalid IPv4 segment: ${segment}`);
|
|
37
|
+
}
|
|
38
|
+
return val;
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
export const isMulticastIpAddress = ({ tag, val: { address } }) => (tag === "ipv4" && address[0] >= 0xe0 && address[0] <= 0xef) ||
|
|
42
|
+
(tag === "ipv6" && (address[0] & 0xff00) === 0xff00);
|
|
43
|
+
export const isIpv4MappedAddress = ({ tag, val: { address } }) => tag === "ipv6" &&
|
|
44
|
+
address[0] === 0 &&
|
|
45
|
+
address[1] === 0 &&
|
|
46
|
+
address[2] === 0 &&
|
|
47
|
+
address[3] === 0 &&
|
|
48
|
+
address[4] === 0 &&
|
|
49
|
+
address[5] === 0xffff;
|
|
50
|
+
export const isWildcardIpAddress = ({ val: { address } }) => address.every((b) => b === 0);
|
|
51
|
+
export const isBroadcastIpAddress = ({ tag, val: { address } }) => tag === "ipv4" && address.every((b) => b === 0xff);
|
|
52
|
+
export const isUnicastIpAddress = (a) => !isMulticastIpAddress(a) && !isBroadcastIpAddress(a) && !isWildcardIpAddress(a);
|
|
53
|
+
export const isLoopbackIpAddress = ({ tag, val: { address } }) => (tag === "ipv4" && address[0] === 127) ||
|
|
54
|
+
(tag === "ipv6" && address.slice(0, 7).every((segment) => segment === 0) && address[7] === 1);
|
|
55
|
+
export const ipAddressConflict = (a, b) => a.tag === b.tag &&
|
|
56
|
+
Number(a.val.port) === Number(b.val.port) &&
|
|
57
|
+
Number(a.val.port) !== 0 &&
|
|
58
|
+
(isWildcardIpAddress(a) ||
|
|
59
|
+
isWildcardIpAddress(b) ||
|
|
60
|
+
a.val.address.every((segment, idx) => segment === b.val.address[idx]));
|
|
61
|
+
/**
|
|
62
|
+
* Serialize a socket‐address to text.
|
|
63
|
+
* @param {{tag:string,val:{address:number[]}}} ipAddr
|
|
64
|
+
* @returns {string|null}
|
|
65
|
+
*/
|
|
66
|
+
export const serializeIpAddress = ({ tag, val: { address } }) => {
|
|
67
|
+
switch (tag) {
|
|
68
|
+
case "ipv4":
|
|
69
|
+
return tupleToIpv4(address);
|
|
70
|
+
case "ipv6":
|
|
71
|
+
return tupleToIpv6(address);
|
|
72
|
+
default:
|
|
73
|
+
throw new Error(`Unknown IP tag: ${tag}`);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Create a socket‐address object.
|
|
78
|
+
* @param {'ipv4'|'ipv6'} family
|
|
79
|
+
* @param {string} host IP text
|
|
80
|
+
* @param {number|bigint} port
|
|
81
|
+
* @returns {{tag:string,val:Object}}
|
|
82
|
+
*/
|
|
83
|
+
export const makeIpAddress = (family, host, port) => {
|
|
84
|
+
let address;
|
|
85
|
+
switch (family) {
|
|
86
|
+
case "ipv4":
|
|
87
|
+
address = ipv4ToTuple(host);
|
|
88
|
+
break;
|
|
89
|
+
case "ipv6":
|
|
90
|
+
address = ipv6ToTuple(host);
|
|
91
|
+
break;
|
|
92
|
+
default:
|
|
93
|
+
throw new Error(`Unknown IP family: ${family}`);
|
|
94
|
+
}
|
|
95
|
+
const base = { address, port };
|
|
96
|
+
return family === "ipv4"
|
|
97
|
+
? { tag: "ipv4", val: base }
|
|
98
|
+
: { tag: "ipv6", val: { ...base, flowInfo: 0, scopeId: 0 } };
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Determine whether an IP address can be used as a local bind address.
|
|
102
|
+
* @param {{tag:'ipv4'|'ipv6',val:{address:number[]}}} ipAddress
|
|
103
|
+
* @returns {boolean}
|
|
104
|
+
*/
|
|
105
|
+
export const isBindableIpAddress = (ipAddress) => {
|
|
106
|
+
if (isWildcardIpAddress(ipAddress)) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
const expectedFamily = ipAddress.tag === "ipv4" ? "IPv4" : "IPv6";
|
|
110
|
+
const expectedAddress = ipAddress.val.address;
|
|
111
|
+
return Object.values(networkInterfaces())
|
|
112
|
+
.flat()
|
|
113
|
+
.some((iface) => {
|
|
114
|
+
if (iface?.family !== expectedFamily) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
const ifaceAddress = ipAddress.tag === "ipv4" ? ipv4ToTuple(iface.address) : ipv6ToTuple(iface.address);
|
|
118
|
+
return expectedAddress.every((segment, idx) => segment === ifaceAddress[idx]);
|
|
119
|
+
});
|
|
120
|
+
};
|