@bytecodealliance/preview2-shim 0.14.1 → 0.14.2
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 +2 -2
- package/lib/browser/filesystem.js +6 -5
- package/lib/browser/random.js +1 -1
- package/lib/io/calls.js +128 -1
- package/lib/io/worker-http.js +159 -65
- package/lib/io/worker-io.js +40 -43
- package/lib/io/worker-socket-tcp.js +131 -0
- package/lib/io/worker-socket-udp.js +219 -0
- package/lib/io/worker-thread.js +288 -82
- package/lib/nodejs/cli.js +27 -11
- package/lib/nodejs/filesystem.js +89 -38
- package/lib/nodejs/http.js +643 -522
- package/lib/nodejs/index.js +0 -1
- package/lib/nodejs/sockets/socket-common.js +15 -2
- package/lib/nodejs/sockets/tcp-socket-impl.js +279 -188
- package/lib/nodejs/sockets/udp-socket-impl.js +305 -165
- package/lib/nodejs/sockets/wasi-sockets.js +54 -33
- package/lib/nodejs/sockets.js +25 -11
- package/lib/synckit/index.js +22 -39
- package/package.json +1 -1
- package/types/interfaces/wasi-http-types.d.ts +53 -41
- package/types/interfaces/wasi-sockets-tcp.d.ts +5 -0
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
WASI Preview2 implementations for Node.js & browsers.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Node.js support is fully tested and conformant against the Wasmtime test suite.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Browser support is considered experimental, and not currently suitable for production applications.
|
|
8
8
|
|
|
9
9
|
# License
|
|
10
10
|
|
|
@@ -39,12 +39,13 @@ function getChildEntry (parentEntry, subpath, openFlags) {
|
|
|
39
39
|
if (!entry || !entry.dir) throw 'not-directory';
|
|
40
40
|
segmentIdx = subpath.indexOf('/');
|
|
41
41
|
const segment = segmentIdx === -1 ? subpath : subpath.slice(0, segmentIdx);
|
|
42
|
-
if (segment === '.' || segment === '') return entry;
|
|
43
42
|
if (segment === '..') throw 'no-entry';
|
|
44
|
-
if (
|
|
43
|
+
if (segment === '.' || segment === '');
|
|
44
|
+
else if (!entry.dir[segment] && openFlags.create)
|
|
45
45
|
entry = entry.dir[segment] = openFlags.directory ? { dir: {} } : { source: new Uint8Array([]) };
|
|
46
46
|
else
|
|
47
47
|
entry = entry.dir[segment];
|
|
48
|
+
subpath = subpath.slice(segmentIdx + 1);
|
|
48
49
|
} while (segmentIdx !== -1)
|
|
49
50
|
if (!entry) throw 'no-entry';
|
|
50
51
|
return entry;
|
|
@@ -179,13 +180,13 @@ class Descriptor {
|
|
|
179
180
|
stat() {
|
|
180
181
|
let type = 'unknown', size = BigInt(0);
|
|
181
182
|
if (this.#entry.source) {
|
|
182
|
-
type = 'directory';
|
|
183
|
-
}
|
|
184
|
-
else if (this.#entry.dir) {
|
|
185
183
|
type = 'regular-file';
|
|
186
184
|
const source = getSource(this.#entry);
|
|
187
185
|
size = BigInt(source.byteLength);
|
|
188
186
|
}
|
|
187
|
+
else if (this.#entry.dir) {
|
|
188
|
+
type = 'directory';
|
|
189
|
+
}
|
|
189
190
|
return {
|
|
190
191
|
type,
|
|
191
192
|
linkCount: BigInt(0),
|
package/lib/browser/random.js
CHANGED
|
@@ -33,7 +33,7 @@ export const random = {
|
|
|
33
33
|
for (var generated = 0; generated < len; generated += MAX_BYTES) {
|
|
34
34
|
// buffer.slice automatically checks if the end is past the end of
|
|
35
35
|
// the buffer so we don't have to here
|
|
36
|
-
crypto.getRandomValues(bytes.
|
|
36
|
+
crypto.getRandomValues(bytes.subarray(generated, generated + MAX_BYTES));
|
|
37
37
|
}
|
|
38
38
|
} else {
|
|
39
39
|
crypto.getRandomValues(bytes);
|
package/lib/io/calls.js
CHANGED
|
@@ -43,13 +43,47 @@ export const FUTURE_DISPOSE = ++call_id << CALL_SHIFT;
|
|
|
43
43
|
// Http
|
|
44
44
|
export const HTTP_CREATE_REQUEST = ++call_id << 24;
|
|
45
45
|
export const HTTP_OUTPUT_STREAM_FINISH = ++call_id << CALL_SHIFT;
|
|
46
|
+
// Http server
|
|
47
|
+
export const HTTP_SERVER_START = ++call_id << CALL_SHIFT;
|
|
48
|
+
export const HTTP_SERVER_STOP = ++call_id << CALL_SHIFT;
|
|
49
|
+
export const HTTP_SERVER_INCOMING_HANDLER = ++call_id << CALL_SHIFT;
|
|
50
|
+
export const HTTP_SERVER_SET_OUTGOING_RESPONSE = ++call_id << CALL_SHIFT;
|
|
51
|
+
export const HTTP_SERVER_CLEAR_OUTGOING_RESPONSE = ++call_id << CALL_SHIFT;
|
|
46
52
|
|
|
47
53
|
// Clocks
|
|
48
54
|
export const CLOCKS_NOW = ++call_id << 24;
|
|
49
55
|
export const CLOCKS_DURATION_SUBSCRIBE = ++call_id << 24;
|
|
50
56
|
export const CLOCKS_INSTANT_SUBSCRIBE = ++call_id << 24;
|
|
51
57
|
|
|
52
|
-
// Sockets
|
|
58
|
+
// Sockets (TCP)
|
|
59
|
+
export const SOCKET_TCP_CREATE_HANDLE = ++call_id << 24;
|
|
60
|
+
export const SOCKET_TCP_BIND = ++call_id << 24;
|
|
61
|
+
export const SOCKET_TCP_CONNECT = ++call_id << 24;
|
|
62
|
+
export const SOCKET_TCP_LISTEN = ++call_id << 24;
|
|
63
|
+
export const SOCKET_TCP_ACCEPT = ++call_id << 24;
|
|
64
|
+
export const SOCKET_TCP_GET_LOCAL_ADDRESS = ++call_id << 24;
|
|
65
|
+
export const SOCKET_TCP_GET_REMOTE_ADDRESS = ++call_id << 24;
|
|
66
|
+
export const SOCKET_TCP_SHUTDOWN = ++call_id << 24;
|
|
67
|
+
export const SOCKET_TCP_SET_KEEP_ALIVE = ++call_id << 24;
|
|
68
|
+
export const SOCKET_TCP_DISPOSE = ++call_id << 24;
|
|
69
|
+
export const SOCKET_TCP_CREATE_INPUT_STREAM = ++call_id << 24;
|
|
70
|
+
export const SOCKET_TCP_CREATE_OUTPUT_STREAM = ++call_id << 24;
|
|
71
|
+
// Sockets (UDP)
|
|
72
|
+
export const SOCKET_UDP_CREATE_HANDLE = ++call_id << 24;
|
|
73
|
+
export const SOCKET_UDP_BIND = ++call_id << 24;
|
|
74
|
+
export const SOCKET_UDP_CONNECT = ++call_id << 24;
|
|
75
|
+
export const SOCKET_UDP_DISCONNECT = ++call_id << 24;
|
|
76
|
+
export const SOCKET_UDP_CHECK_SEND = ++call_id << 24;
|
|
77
|
+
export const SOCKET_UDP_SEND = ++call_id << 24;
|
|
78
|
+
export const SOCKET_UDP_RECEIVE = ++call_id << 24;
|
|
79
|
+
export const SOCKET_UDP_DISPOSE = ++call_id << 24;
|
|
80
|
+
export const SOCKET_UDP_GET_LOCAL_ADDRESS = ++call_id << 24;
|
|
81
|
+
export const SOCKET_UDP_GET_REMOTE_ADDRESS = ++call_id << 24;
|
|
82
|
+
export const SOCKET_UDP_GET_RECEIVE_BUFFER_SIZE = ++call_id << 24;
|
|
83
|
+
export const SOCKET_UDP_SET_RECEIVE_BUFFER_SIZE = ++call_id << 24;
|
|
84
|
+
export const SOCKET_UDP_GET_SEND_BUFFER_SIZE = ++call_id << 24;
|
|
85
|
+
export const SOCKET_UDP_SET_SEND_BUFFER_SIZE = ++call_id << 24;
|
|
86
|
+
export const SOCKET_UDP_SET_UNICAST_HOP_LIMIT = ++call_id << 24;
|
|
53
87
|
export const SOCKET_RESOLVE_ADDRESS_CREATE_REQUEST = ++call_id << 24;
|
|
54
88
|
export const SOCKET_RESOLVE_ADDRESS_GET_AND_DISPOSE_REQUEST = ++call_id << 24;
|
|
55
89
|
export const SOCKET_RESOLVE_ADDRESS_DISPOSE_REQUEST = ++call_id << 24;
|
|
@@ -62,3 +96,96 @@ export const STDERR = ++cnt;
|
|
|
62
96
|
export const FILE = ++cnt;
|
|
63
97
|
export const HTTP = ++cnt;
|
|
64
98
|
export const SOCKET = ++cnt;
|
|
99
|
+
|
|
100
|
+
export const callTypeMap = {
|
|
101
|
+
[STDIN]: "STDIN",
|
|
102
|
+
[STDOUT]: "STDOUT",
|
|
103
|
+
[STDERR]: "STDERR",
|
|
104
|
+
[FILE]: "FILE",
|
|
105
|
+
[HTTP]: "HTTP",
|
|
106
|
+
[SOCKET]: "SOCKET",
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const callMap = {
|
|
110
|
+
[INPUT_STREAM_CREATE]: 'INPUT_STREAM_CREATE',
|
|
111
|
+
[INPUT_STREAM_READ]: "INPUT_STREAM_READ",
|
|
112
|
+
[INPUT_STREAM_BLOCKING_READ]: "INPUT_STREAM_BLOCKING_READ",
|
|
113
|
+
[INPUT_STREAM_SKIP]: "INPUT_STREAM_SKIP",
|
|
114
|
+
[INPUT_STREAM_BLOCKING_SKIP]: "INPUT_STREAM_BLOCKING_SKIP",
|
|
115
|
+
[INPUT_STREAM_SUBSCRIBE]: "INPUT_STREAM_SUBSCRIBE",
|
|
116
|
+
[INPUT_STREAM_DISPOSE]: "INPUT_STREAM_DISPOSE",
|
|
117
|
+
|
|
118
|
+
// Io Output Stream
|
|
119
|
+
[OUTPUT_STREAM_CREATE]: "OUTPUT_STREAM_CREATE",
|
|
120
|
+
[OUTPUT_STREAM_CHECK_WRITE]: "OUTPUT_STREAM_CHECK_WRITE",
|
|
121
|
+
[OUTPUT_STREAM_WRITE]: "OUTPUT_STREAM_WRITE",
|
|
122
|
+
[OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH]: "OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH",
|
|
123
|
+
[OUTPUT_STREAM_FLUSH]: "OUTPUT_STREAM_FLUSH",
|
|
124
|
+
[OUTPUT_STREAM_BLOCKING_FLUSH]: "OUTPUT_STREAM_BLOCKING_FLUSH",
|
|
125
|
+
[OUTPUT_STREAM_WRITE_ZEROES]: "OUTPUT_STREAM_WRITE_ZEROES",
|
|
126
|
+
[OUTPUT_STREAM_BLOCKING_WRITE_ZEROES_AND_FLUSH]: "OUTPUT_STREAM_BLOCKING_WRITE_ZEROES_AND_FLUSH",
|
|
127
|
+
[OUTPUT_STREAM_SPLICE]: "OUTPUT_STREAM_SPLICE",
|
|
128
|
+
[OUTPUT_STREAM_BLOCKING_SPLICE]: "OUTPUT_STREAM_BLOCKING_SPLICE",
|
|
129
|
+
[OUTPUT_STREAM_SUBSCRIBE]: "OUTPUT_STREAM_SUBSCRIBE",
|
|
130
|
+
[OUTPUT_STREAM_DISPOSE]: "OUTPUT_STREAM_DISPOSE",
|
|
131
|
+
|
|
132
|
+
[OUTPUT_STREAM_GET_TOTAL_BYTES]: "OUTPUT_STREAM_GET_TOTAL_BYTES",
|
|
133
|
+
|
|
134
|
+
// Io Poll
|
|
135
|
+
[POLL_POLLABLE_READY]: "POLL_POLLABLE_READY",
|
|
136
|
+
[POLL_POLLABLE_BLOCK]: "POLL_POLLABLE_BLOCK",
|
|
137
|
+
[POLL_POLL_LIST]: "POLL_POLL_LIST",
|
|
138
|
+
|
|
139
|
+
// Futures
|
|
140
|
+
[FUTURE_GET_VALUE_AND_DISPOSE]: "FUTURE_GET_VALUE_AND_DISPOSE",
|
|
141
|
+
[FUTURE_DISPOSE]: "FUTURE_DISPOSE",
|
|
142
|
+
|
|
143
|
+
// Http
|
|
144
|
+
[HTTP_CREATE_REQUEST]: "HTTP_CREATE_REQUEST",
|
|
145
|
+
[HTTP_OUTPUT_STREAM_FINISH]: "HTTP_OUTPUT_STREAM_FINISH",
|
|
146
|
+
// Http server
|
|
147
|
+
[HTTP_SERVER_START]: "HTTP_SERVER_START",
|
|
148
|
+
[HTTP_SERVER_STOP]: "HTTP_SERVER_STOP",
|
|
149
|
+
[HTTP_SERVER_INCOMING_HANDLER]: "HTTP_SERVER_INCOMING_HANDLER",
|
|
150
|
+
[HTTP_SERVER_SET_OUTGOING_RESPONSE]: "HTTP_SERVER_SET_OUTGOING_RESPONSE",
|
|
151
|
+
[HTTP_SERVER_CLEAR_OUTGOING_RESPONSE]: "HTTP_SERVER_CLEAR_OUTGOING_RESPONSE",
|
|
152
|
+
|
|
153
|
+
// Clocks
|
|
154
|
+
[CLOCKS_NOW]: "CLOCKS_NOW",
|
|
155
|
+
[CLOCKS_DURATION_SUBSCRIBE]: "CLOCKS_DURATION_SUBSCRIBE",
|
|
156
|
+
[CLOCKS_INSTANT_SUBSCRIBE]: "CLOCKS_INSTANT_SUBSCRIBE",
|
|
157
|
+
|
|
158
|
+
// Sockets TCP
|
|
159
|
+
[SOCKET_TCP_CREATE_HANDLE]: "SOCKET_TCP_CREATE_HANDLE",
|
|
160
|
+
[SOCKET_TCP_BIND]: "SOCKET_TCP_BIND",
|
|
161
|
+
[SOCKET_TCP_CONNECT]: "SOCKET_TCP_CONNECT",
|
|
162
|
+
[SOCKET_TCP_LISTEN]: "SOCKET_TCP_LISTEN",
|
|
163
|
+
[SOCKET_TCP_ACCEPT]: "SOCKET_TCP_ACCEPT",
|
|
164
|
+
[SOCKET_TCP_GET_LOCAL_ADDRESS]: "SOCKET_TCP_GET_LOCAL_ADDRESS",
|
|
165
|
+
[SOCKET_TCP_GET_REMOTE_ADDRESS]: "SOCKET_TCP_GET_REMOTE_ADDRESS",
|
|
166
|
+
[SOCKET_TCP_SHUTDOWN]: "SOCKET_TCP_SHUTDOWN",
|
|
167
|
+
[SOCKET_TCP_SET_KEEP_ALIVE]: "SOCKET_TCP_SET_KEEP_ALIVE",
|
|
168
|
+
[SOCKET_TCP_DISPOSE]: "SOCKET_TCP_DISPOSE",
|
|
169
|
+
[SOCKET_TCP_CREATE_INPUT_STREAM]: "SOCKET_TCP_CREATE_INPUT_STREAM",
|
|
170
|
+
[SOCKET_TCP_CREATE_OUTPUT_STREAM]: "SOCKET_TCP_CREATE_OUTPUT_STREAM",
|
|
171
|
+
// Sockets UDP
|
|
172
|
+
[SOCKET_UDP_CREATE_HANDLE]: "SOCKET_UDP_CREATE_HANDLE",
|
|
173
|
+
[SOCKET_UDP_BIND]: "SOCKET_UDP_BIND",
|
|
174
|
+
[SOCKET_UDP_CONNECT]: "SOCKET_UDP_CONNECT",
|
|
175
|
+
[SOCKET_UDP_DISCONNECT]: "SOCKET_UDP_DISCONNECT",
|
|
176
|
+
[SOCKET_UDP_CHECK_SEND]: "SOCKET_UDP_CHECK_SEND",
|
|
177
|
+
[SOCKET_UDP_SEND]: "SOCKET_UDP_SEND",
|
|
178
|
+
[SOCKET_UDP_RECEIVE]: "SOCKET_UDP_RECEIVE",
|
|
179
|
+
[SOCKET_UDP_DISPOSE]: "SOCKET_UDP_DISPOSE",
|
|
180
|
+
[SOCKET_UDP_GET_LOCAL_ADDRESS]: "SOCKET_UDP_GET_LOCAL_ADDRESS",
|
|
181
|
+
[SOCKET_UDP_GET_REMOTE_ADDRESS]: "SOCKET_UDP_GET_REMOTE_ADDRESS",
|
|
182
|
+
[SOCKET_UDP_GET_RECEIVE_BUFFER_SIZE]: "SOCKET_UDP_GET_RECEIVE_BUFFER_SIZE",
|
|
183
|
+
[SOCKET_UDP_SET_RECEIVE_BUFFER_SIZE]: "SOCKET_UDP_SET_RECEIVE_BUFFER_SIZE",
|
|
184
|
+
[SOCKET_UDP_GET_SEND_BUFFER_SIZE]: "SOCKET_UDP_GET_SEND_BUFFER_SIZE",
|
|
185
|
+
[SOCKET_UDP_SET_SEND_BUFFER_SIZE]: "SOCKET_UDP_SET_SEND_BUFFER_SIZE",
|
|
186
|
+
[SOCKET_UDP_SET_UNICAST_HOP_LIMIT]: "SOCKET_UDP_SET_UNICAST_HOP_LIMIT",
|
|
187
|
+
// Socket DNS
|
|
188
|
+
[SOCKET_RESOLVE_ADDRESS_CREATE_REQUEST]: "SOCKET_RESOLVE_ADDRESS_CREATE_REQUEST",
|
|
189
|
+
[SOCKET_RESOLVE_ADDRESS_GET_AND_DISPOSE_REQUEST]: "SOCKET_RESOLVE_ADDRESS_GET_AND_DISPOSE_REQUEST",
|
|
190
|
+
[SOCKET_RESOLVE_ADDRESS_DISPOSE_REQUEST]: "SOCKET_RESOLVE_ADDRESS_DISPOSE_REQUEST",
|
|
191
|
+
};
|
package/lib/io/worker-http.js
CHANGED
|
@@ -1,14 +1,91 @@
|
|
|
1
|
-
import { Readable } from "node:stream";
|
|
2
1
|
import { createStream, getStreamOrThrow } from "./worker-thread.js";
|
|
2
|
+
import {
|
|
3
|
+
createServer,
|
|
4
|
+
request as httpRequest,
|
|
5
|
+
Agent as HttpAgent,
|
|
6
|
+
} from "node:http";
|
|
7
|
+
import { request as httpsRequest, Agent as HttpsAgent } from "node:https";
|
|
8
|
+
import { parentPort } from "node:worker_threads";
|
|
9
|
+
import { HTTP_SERVER_INCOMING_HANDLER } from "./calls.js";
|
|
3
10
|
|
|
4
|
-
|
|
5
|
-
|
|
11
|
+
const agentOptions = {
|
|
12
|
+
keepAlive: true,
|
|
13
|
+
};
|
|
14
|
+
const httpAgent = new HttpAgent(agentOptions);
|
|
15
|
+
const httpsAgent = new HttpsAgent(agentOptions);
|
|
16
|
+
|
|
17
|
+
const servers = new Map();
|
|
18
|
+
|
|
19
|
+
let responseCnt = 0;
|
|
20
|
+
const responses = new Map();
|
|
21
|
+
|
|
22
|
+
export async function stopHttpServer(id) {
|
|
23
|
+
await new Promise((resolve) => servers.get(id).close(resolve));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function clearOutgoingResponse(id) {
|
|
27
|
+
responses.delete(id);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function setOutgoingResponse(
|
|
31
|
+
id,
|
|
32
|
+
{ statusCode, headers, streamId }
|
|
33
|
+
) {
|
|
34
|
+
const response = responses.get(id);
|
|
35
|
+
const textDecoder = new TextDecoder();
|
|
36
|
+
response.writeHead(
|
|
37
|
+
statusCode,
|
|
38
|
+
Object.fromEntries(
|
|
39
|
+
headers.map(([key, val]) => [key, textDecoder.decode(val)])
|
|
40
|
+
)
|
|
41
|
+
);
|
|
42
|
+
response.flushHeaders();
|
|
43
|
+
const { stream } = getStreamOrThrow(streamId);
|
|
44
|
+
stream.pipe(response);
|
|
45
|
+
responses.delete(id);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function startHttpServer(id, { port, host }) {
|
|
49
|
+
const server = createServer((req, res) => {
|
|
50
|
+
// create the streams and their ids
|
|
51
|
+
const streamId = createStream(req);
|
|
52
|
+
const responseId = ++responseCnt;
|
|
53
|
+
parentPort.postMessage({
|
|
54
|
+
type: HTTP_SERVER_INCOMING_HANDLER,
|
|
55
|
+
id,
|
|
56
|
+
payload: {
|
|
57
|
+
responseId,
|
|
58
|
+
method: req.method,
|
|
59
|
+
host: req.headers.host || host || 'localhost',
|
|
60
|
+
pathWithQuery: req.url,
|
|
61
|
+
headers: Object.entries(req.headersDistinct).flatMap(([key, val]) => val.map(val => [key, val])),
|
|
62
|
+
streamId,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
responses.set(responseId, res);
|
|
66
|
+
});
|
|
67
|
+
await new Promise((resolve, reject) => {
|
|
68
|
+
server.listen(port, host, resolve);
|
|
69
|
+
server.on("error", reject);
|
|
70
|
+
});
|
|
71
|
+
servers.set(id, server);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function createHttpRequest(
|
|
75
|
+
method,
|
|
76
|
+
scheme,
|
|
77
|
+
authority,
|
|
78
|
+
pathWithQuery,
|
|
79
|
+
headers,
|
|
80
|
+
bodyId,
|
|
81
|
+
connectTimeout,
|
|
82
|
+
betweenBytesTimeout,
|
|
83
|
+
firstByteTimeout
|
|
84
|
+
) {
|
|
85
|
+
let stream = null;
|
|
6
86
|
if (bodyId) {
|
|
7
87
|
try {
|
|
8
|
-
|
|
9
|
-
body = stream.readableBodyStream;
|
|
10
|
-
// this indicates we're attached
|
|
11
|
-
stream.readableBodyStream = null;
|
|
88
|
+
({ stream } = getStreamOrThrow(bodyId));
|
|
12
89
|
} catch (e) {
|
|
13
90
|
if (e.tag === "closed")
|
|
14
91
|
throw { tag: "internal-error", val: "Unexpected closed body stream" };
|
|
@@ -25,71 +102,88 @@ export async function createHttpRequest(method, url, headers, bodyId) {
|
|
|
25
102
|
}
|
|
26
103
|
}
|
|
27
104
|
try {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
105
|
+
// Make a request
|
|
106
|
+
let req;
|
|
107
|
+
switch (scheme) {
|
|
108
|
+
case "http:":
|
|
109
|
+
req = httpRequest({
|
|
110
|
+
agent: httpAgent,
|
|
111
|
+
method,
|
|
112
|
+
host: authority.split(':')[0],
|
|
113
|
+
port: authority.split(':')[1],
|
|
114
|
+
path: pathWithQuery,
|
|
115
|
+
timeout: connectTimeout && Number(connectTimeout),
|
|
116
|
+
});
|
|
117
|
+
break;
|
|
118
|
+
case "https:":
|
|
119
|
+
req = httpsRequest({
|
|
120
|
+
agent: httpsAgent,
|
|
121
|
+
method,
|
|
122
|
+
host: authority.split(':')[0],
|
|
123
|
+
port: authority.split(':')[1],
|
|
124
|
+
path: pathWithQuery,
|
|
125
|
+
timeout: connectTimeout && Number(connectTimeout),
|
|
126
|
+
});
|
|
127
|
+
break;
|
|
128
|
+
default:
|
|
129
|
+
throw { tag: "HTTP-protocol-error" };
|
|
130
|
+
}
|
|
131
|
+
for (const [key, value] of headers) {
|
|
132
|
+
req.appendHeader(key, value);
|
|
133
|
+
}
|
|
134
|
+
req.flushHeaders();
|
|
135
|
+
if (stream) {
|
|
136
|
+
stream.pipe(req);
|
|
137
|
+
} else {
|
|
138
|
+
req.end();
|
|
139
|
+
}
|
|
140
|
+
const res = await new Promise((resolve, reject) => {
|
|
141
|
+
req.on("response", resolve);
|
|
142
|
+
req.on("close", () => reject);
|
|
143
|
+
req.on("error", reject);
|
|
34
144
|
});
|
|
35
|
-
|
|
145
|
+
if (firstByteTimeout)
|
|
146
|
+
res.setTimeout(Number(firstByteTimeout));
|
|
147
|
+
if (betweenBytesTimeout)
|
|
148
|
+
res.on("readable", () => {
|
|
149
|
+
res.setTimeout(Number(betweenBytesTimeout));
|
|
150
|
+
});
|
|
151
|
+
res.on("end", () => void res.emit("readable"));
|
|
152
|
+
const bodyStreamId = createStream(res);
|
|
36
153
|
return {
|
|
37
|
-
status: res.
|
|
38
|
-
headers: Array.from(res.headers),
|
|
154
|
+
status: res.statusCode,
|
|
155
|
+
headers: Array.from(Object.entries(res.headers)),
|
|
39
156
|
bodyStreamId: bodyStreamId,
|
|
40
157
|
};
|
|
41
158
|
} catch (e) {
|
|
42
|
-
if (e?.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
159
|
+
if (e?.tag) throw e;
|
|
160
|
+
const err = getFirstError(e);
|
|
161
|
+
switch (err.code) {
|
|
162
|
+
case "ECONNRESET":
|
|
163
|
+
throw { tag: "HTTP-protocol-error" };
|
|
164
|
+
case "ENOTFOUND":
|
|
46
165
|
throw {
|
|
47
|
-
tag: "
|
|
166
|
+
tag: "DNS-error",
|
|
167
|
+
val: {
|
|
168
|
+
rcode: err.code,
|
|
169
|
+
infoCode: err.errno,
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
case "ECONNREFUSED":
|
|
173
|
+
throw {
|
|
174
|
+
tag: "connection-refused",
|
|
48
175
|
};
|
|
49
|
-
switch (err.syscall) {
|
|
50
|
-
case "connect": {
|
|
51
|
-
if (err.code === "ECONNREFUSED")
|
|
52
|
-
throw {
|
|
53
|
-
tag: "connection-refused",
|
|
54
|
-
};
|
|
55
|
-
break;
|
|
56
|
-
}
|
|
57
|
-
case "getaddrinfo": {
|
|
58
|
-
const { errno, code } = err;
|
|
59
|
-
throw {
|
|
60
|
-
tag: "DNS-error",
|
|
61
|
-
val: {
|
|
62
|
-
rcode: code,
|
|
63
|
-
infoCode: errno,
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
if (e?.message?.includes("Failed to parse URL")) {
|
|
70
|
-
throw {
|
|
71
|
-
tag: "HTTP-request-URI-invalid",
|
|
72
|
-
val: undefined,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
if (e?.message?.includes("HTTP")) {
|
|
76
|
-
switch (e?.message.replace(/'[^']+'/, "'{}'")) {
|
|
77
|
-
case "'{}' HTTP method is unsupported.":
|
|
78
|
-
throw {
|
|
79
|
-
tag: "HTTP-protocol-error",
|
|
80
|
-
val: undefined,
|
|
81
|
-
};
|
|
82
|
-
case "'{}' is not a valid HTTP method.":
|
|
83
|
-
throw {
|
|
84
|
-
tag: "HTTP-request-method-invalid",
|
|
85
|
-
val: undefined,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
throw {
|
|
89
|
-
tag: "internal-error",
|
|
90
|
-
val: e.toString(),
|
|
91
|
-
};
|
|
92
176
|
}
|
|
93
|
-
throw
|
|
177
|
+
throw {
|
|
178
|
+
tag: "internal-error",
|
|
179
|
+
val: err.toString(),
|
|
180
|
+
};
|
|
94
181
|
}
|
|
95
182
|
}
|
|
183
|
+
|
|
184
|
+
function getFirstError(e) {
|
|
185
|
+
if (typeof e !== "object" || e === null) return e;
|
|
186
|
+
if (e.cause) return getFirstError(e.cause);
|
|
187
|
+
if (e instanceof AggregateError) return getFirstError(e.errors[0]);
|
|
188
|
+
return e;
|
|
189
|
+
}
|
package/lib/io/worker-io.js
CHANGED
|
@@ -2,7 +2,6 @@ import { fileURLToPath } from "node:url";
|
|
|
2
2
|
import { createSyncFn } from "../synckit/index.js";
|
|
3
3
|
import {
|
|
4
4
|
CALL_MASK,
|
|
5
|
-
CALL_SHIFT,
|
|
6
5
|
CALL_TYPE_MASK,
|
|
7
6
|
INPUT_STREAM_BLOCKING_READ,
|
|
8
7
|
INPUT_STREAM_BLOCKING_SKIP,
|
|
@@ -24,6 +23,9 @@ import {
|
|
|
24
23
|
POLL_POLL_LIST,
|
|
25
24
|
POLL_POLLABLE_BLOCK,
|
|
26
25
|
POLL_POLLABLE_READY,
|
|
26
|
+
HTTP_SERVER_INCOMING_HANDLER,
|
|
27
|
+
callTypeMap,
|
|
28
|
+
callMap
|
|
27
29
|
} from "./calls.js";
|
|
28
30
|
import { STDERR } from "./calls.js";
|
|
29
31
|
|
|
@@ -33,18 +35,37 @@ const workerPath = fileURLToPath(
|
|
|
33
35
|
new URL("./worker-thread.js", import.meta.url)
|
|
34
36
|
);
|
|
35
37
|
|
|
38
|
+
const httpIncomingHandlers = new Map();
|
|
39
|
+
export function registerIncomingHttpHandler (id, handler) {
|
|
40
|
+
httpIncomingHandlers.set(id, handler);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const instanceId = Math.round(Math.random() * 1000).toString();
|
|
44
|
+
|
|
36
45
|
/**
|
|
37
46
|
* @type {(call: number, id: number | null, payload: any) -> any}
|
|
38
47
|
*/
|
|
39
|
-
export let ioCall = createSyncFn(workerPath)
|
|
48
|
+
export let ioCall = createSyncFn(workerPath, (type, id, payload) => {
|
|
49
|
+
// 'callbacks' from the worker
|
|
50
|
+
// ONLY happens for an http server incoming handler, and NOTHING else (not even sockets, since accept is sync!)
|
|
51
|
+
if (type !== HTTP_SERVER_INCOMING_HANDLER)
|
|
52
|
+
throw new Error('Internal error: only incoming handler callback is permitted');
|
|
53
|
+
const handler = httpIncomingHandlers.get(id);
|
|
54
|
+
if (!handler)
|
|
55
|
+
throw new Error(`Internal error: no incoming handler registered for server ${id}`);
|
|
56
|
+
handler(payload);
|
|
57
|
+
});
|
|
40
58
|
if (DEBUG) {
|
|
41
59
|
const _ioCall = ioCall;
|
|
42
60
|
ioCall = function ioCall(num, id, payload) {
|
|
61
|
+
if (typeof id !== 'number' && id !== null)
|
|
62
|
+
throw new Error('id must be a number or null');
|
|
43
63
|
let ret;
|
|
44
64
|
try {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
num &
|
|
65
|
+
console.error(
|
|
66
|
+
instanceId,
|
|
67
|
+
callMap[(num & CALL_MASK)],
|
|
68
|
+
callTypeMap[num & CALL_TYPE_MASK],
|
|
48
69
|
id,
|
|
49
70
|
payload
|
|
50
71
|
);
|
|
@@ -54,7 +75,7 @@ if (DEBUG) {
|
|
|
54
75
|
ret = e;
|
|
55
76
|
throw ret;
|
|
56
77
|
} finally {
|
|
57
|
-
|
|
78
|
+
console.error(instanceId, "->", ret);
|
|
58
79
|
}
|
|
59
80
|
};
|
|
60
81
|
}
|
|
@@ -63,6 +84,10 @@ const symbolDispose = Symbol.dispose || Symbol.for("dispose");
|
|
|
63
84
|
|
|
64
85
|
const _Error = Error;
|
|
65
86
|
const IoError = class Error extends _Error {
|
|
87
|
+
constructor (payload) {
|
|
88
|
+
super(payload);
|
|
89
|
+
this.payload = payload;
|
|
90
|
+
}
|
|
66
91
|
toDebugString() {
|
|
67
92
|
return this.message;
|
|
68
93
|
}
|
|
@@ -72,10 +97,9 @@ function streamIoErrorCall(call, id, payload) {
|
|
|
72
97
|
try {
|
|
73
98
|
return ioCall(call, id, payload);
|
|
74
99
|
} catch (e) {
|
|
75
|
-
if (e.tag ===
|
|
76
|
-
throw e;
|
|
100
|
+
if (e.tag === "closed") throw e;
|
|
77
101
|
if (e.tag === "last-operation-failed") {
|
|
78
|
-
e.val = new IoError(e.val);
|
|
102
|
+
e.val = new IoError(Object.assign(new Error(e.val.message), e.val));
|
|
79
103
|
throw e;
|
|
80
104
|
}
|
|
81
105
|
// any invalid error is a trap
|
|
@@ -206,16 +230,14 @@ class OutputStream {
|
|
|
206
230
|
return streamIoErrorCall(
|
|
207
231
|
OUTPUT_STREAM_SPLICE | this.#streamType,
|
|
208
232
|
this.#id,
|
|
209
|
-
src.#id,
|
|
210
|
-
len
|
|
233
|
+
{ src: src.#id, len }
|
|
211
234
|
);
|
|
212
235
|
}
|
|
213
236
|
blockingSplice(src, len) {
|
|
214
237
|
return streamIoErrorCall(
|
|
215
238
|
OUTPUT_STREAM_BLOCKING_SPLICE | this.#streamType,
|
|
216
239
|
this.#id,
|
|
217
|
-
src
|
|
218
|
-
len
|
|
240
|
+
{ src: inputStreamId(src), len }
|
|
219
241
|
);
|
|
220
242
|
}
|
|
221
243
|
subscribe() {
|
|
@@ -254,21 +276,16 @@ export const streams = { InputStream, OutputStream };
|
|
|
254
276
|
|
|
255
277
|
class Pollable {
|
|
256
278
|
#id;
|
|
257
|
-
#ready = false;
|
|
258
279
|
get _id() {
|
|
259
280
|
return this.#id;
|
|
260
281
|
}
|
|
261
282
|
ready() {
|
|
262
|
-
if (this.#
|
|
263
|
-
|
|
264
|
-
if (ready) this.#ready = true;
|
|
265
|
-
return ready;
|
|
283
|
+
if (this.#id === 0) return true;
|
|
284
|
+
return ioCall(POLL_POLLABLE_READY, this.#id);
|
|
266
285
|
}
|
|
267
286
|
block() {
|
|
268
|
-
if (
|
|
269
|
-
|
|
270
|
-
this.#ready = true;
|
|
271
|
-
}
|
|
287
|
+
if (this.#id === 0) return;
|
|
288
|
+
ioCall(POLL_POLLABLE_BLOCK, this.#id);
|
|
272
289
|
}
|
|
273
290
|
static _getId(pollable) {
|
|
274
291
|
return pollable.#id;
|
|
@@ -276,40 +293,20 @@ class Pollable {
|
|
|
276
293
|
static _create(id) {
|
|
277
294
|
const pollable = new Pollable();
|
|
278
295
|
pollable.#id = id;
|
|
279
|
-
if (id === 0) pollable.#ready = true;
|
|
280
296
|
return pollable;
|
|
281
297
|
}
|
|
282
|
-
static _listToIds(list) {
|
|
283
|
-
return list.map((pollable) => pollable.#id);
|
|
284
|
-
}
|
|
285
|
-
static _markReady(pollable) {
|
|
286
|
-
pollable.#ready = true;
|
|
287
|
-
}
|
|
288
298
|
}
|
|
289
299
|
|
|
290
300
|
export const pollableCreate = Pollable._create;
|
|
291
301
|
delete Pollable._create;
|
|
292
302
|
|
|
293
|
-
const pollableListToIds = Pollable._listToIds;
|
|
294
|
-
delete Pollable._listToIds;
|
|
295
|
-
|
|
296
|
-
const pollableMarkReady = Pollable._markReady;
|
|
297
|
-
delete Pollable._markReady;
|
|
298
|
-
|
|
299
303
|
const pollableGetId = Pollable._getId;
|
|
300
304
|
delete Pollable._getId;
|
|
301
305
|
|
|
302
306
|
export const poll = {
|
|
303
307
|
Pollable,
|
|
304
308
|
poll(list) {
|
|
305
|
-
|
|
306
|
-
return list.filter((pollable) => {
|
|
307
|
-
if (includeList.includes(pollableGetId(pollable))) {
|
|
308
|
-
pollableMarkReady(pollable);
|
|
309
|
-
return true;
|
|
310
|
-
}
|
|
311
|
-
return false;
|
|
312
|
-
});
|
|
309
|
+
return ioCall(POLL_POLL_LIST, null, list.map(pollableGetId));
|
|
313
310
|
},
|
|
314
311
|
};
|
|
315
312
|
|