@bytecodealliance/preview2-shim 0.17.1 → 0.17.3
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/lib/browser/cli.js +91 -94
- package/lib/browser/clocks.js +30 -29
- package/lib/browser/filesystem.js +298 -251
- package/lib/browser/http.js +129 -128
- package/lib/browser/index.js +8 -16
- package/lib/browser/io.js +143 -135
- package/lib/browser/random.js +44 -42
- package/lib/browser/sockets.js +68 -166
- package/lib/common/instantiation.js +127 -0
- package/lib/io/calls.js +7 -5
- package/lib/io/worker-http.js +175 -157
- package/lib/io/worker-io.js +402 -386
- package/lib/io/worker-socket-tcp.js +271 -219
- package/lib/io/worker-socket-udp.js +494 -429
- package/lib/io/worker-sockets.js +276 -262
- package/lib/io/worker-thread.js +946 -815
- package/lib/nodejs/cli.js +64 -63
- package/lib/nodejs/clocks.js +51 -45
- package/lib/nodejs/filesystem.js +788 -654
- package/lib/nodejs/http.js +693 -617
- package/lib/nodejs/index.js +8 -16
- package/lib/nodejs/random.js +32 -28
- package/lib/nodejs/sockets.js +538 -474
- package/lib/synckit/index.js +94 -85
- package/package.json +9 -5
- package/types/cli.d.ts +11 -23
- package/types/clocks.d.ts +2 -5
- package/types/filesystem.d.ts +2 -5
- package/types/http.d.ts +3 -7
- package/types/index.d.ts +6 -15
- package/types/instantiation.d.ts +112 -0
- package/types/interfaces/wasi-cli-environment.d.ts +21 -22
- package/types/interfaces/wasi-cli-exit.d.ts +5 -6
- package/types/interfaces/wasi-cli-run.d.ts +5 -6
- package/types/interfaces/wasi-cli-stderr.d.ts +3 -5
- package/types/interfaces/wasi-cli-stdin.d.ts +3 -5
- package/types/interfaces/wasi-cli-stdout.d.ts +3 -5
- package/types/interfaces/wasi-cli-terminal-input.d.ts +5 -3
- package/types/interfaces/wasi-cli-terminal-output.d.ts +5 -3
- package/types/interfaces/wasi-cli-terminal-stderr.d.ts +7 -9
- package/types/interfaces/wasi-cli-terminal-stdin.d.ts +7 -9
- package/types/interfaces/wasi-cli-terminal-stdout.d.ts +7 -9
- package/types/interfaces/wasi-clocks-monotonic-clock.d.ts +24 -26
- package/types/interfaces/wasi-clocks-wall-clock.d.ts +23 -24
- package/types/interfaces/wasi-filesystem-preopens.d.ts +6 -8
- package/types/interfaces/wasi-filesystem-types.d.ts +34 -33
- package/types/interfaces/wasi-http-incoming-handler.d.ts +16 -19
- package/types/interfaces/wasi-http-outgoing-handler.d.ts +18 -23
- package/types/interfaces/wasi-http-types.d.ts +49 -38
- package/types/interfaces/wasi-io-error.d.ts +5 -3
- package/types/interfaces/wasi-io-poll.d.ts +27 -25
- package/types/interfaces/wasi-io-streams.d.ts +24 -21
- package/types/interfaces/wasi-random-insecure-seed.d.ts +21 -22
- package/types/interfaces/wasi-random-insecure.d.ts +19 -20
- package/types/interfaces/wasi-random-random.d.ts +23 -24
- package/types/interfaces/wasi-sockets-instance-network.d.ts +6 -8
- package/types/interfaces/wasi-sockets-ip-name-lookup.d.ts +32 -34
- package/types/interfaces/wasi-sockets-network.d.ts +5 -3
- package/types/interfaces/wasi-sockets-tcp-create-socket.d.ts +28 -33
- package/types/interfaces/wasi-sockets-tcp.d.ts +17 -23
- package/types/interfaces/wasi-sockets-udp-create-socket.d.ts +28 -33
- package/types/interfaces/wasi-sockets-udp.d.ts +20 -17
- package/types/io.d.ts +3 -7
- package/types/random.d.ts +3 -7
- package/types/sockets.d.ts +7 -15
- package/types/wasi-cli-command.d.ts +29 -29
- package/types/wasi-http-proxy.d.ts +13 -13
package/lib/nodejs/http.js
CHANGED
|
@@ -1,176 +1,191 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from
|
|
2
|
+
FUTURE_DISPOSE,
|
|
3
|
+
FUTURE_SUBSCRIBE,
|
|
4
|
+
FUTURE_TAKE_VALUE,
|
|
5
|
+
HTTP_CREATE_REQUEST,
|
|
6
|
+
HTTP_OUTGOING_BODY_DISPOSE,
|
|
7
|
+
HTTP_OUTPUT_STREAM_FINISH,
|
|
8
|
+
HTTP_SERVER_CLEAR_OUTGOING_RESPONSE,
|
|
9
|
+
HTTP_SERVER_SET_OUTGOING_RESPONSE,
|
|
10
|
+
HTTP_SERVER_START,
|
|
11
|
+
HTTP_SERVER_STOP,
|
|
12
|
+
OUTPUT_STREAM_CREATE,
|
|
13
|
+
OUTPUT_STREAM_DISPOSE,
|
|
14
|
+
} from '../io/calls.js';
|
|
15
15
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} from
|
|
24
|
-
import { HTTP } from
|
|
25
|
-
|
|
26
|
-
import * as http from
|
|
16
|
+
earlyDispose,
|
|
17
|
+
inputStreamCreate,
|
|
18
|
+
ioCall,
|
|
19
|
+
outputStreamCreate,
|
|
20
|
+
pollableCreate,
|
|
21
|
+
registerDispose,
|
|
22
|
+
registerIncomingHttpHandler,
|
|
23
|
+
} from '../io/worker-io.js';
|
|
24
|
+
import { HTTP } from '../io/calls.js';
|
|
25
|
+
|
|
26
|
+
import * as http from 'node:http';
|
|
27
27
|
const { validateHeaderName = () => {}, validateHeaderValue = () => {} } = http;
|
|
28
28
|
|
|
29
|
-
const symbolDispose = Symbol.dispose || Symbol.for(
|
|
30
|
-
export const _forbiddenHeaders = new Set([
|
|
29
|
+
const symbolDispose = Symbol.dispose || Symbol.for('dispose');
|
|
30
|
+
export const _forbiddenHeaders = new Set(['connection', 'keep-alive', 'host']);
|
|
31
31
|
|
|
32
32
|
class IncomingBody {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
33
|
+
#finished = false;
|
|
34
|
+
#stream = undefined;
|
|
35
|
+
stream() {
|
|
36
|
+
if (!this.#stream) {
|
|
37
|
+
throw undefined;
|
|
38
|
+
}
|
|
39
|
+
const stream = this.#stream;
|
|
40
|
+
this.#stream = null;
|
|
41
|
+
return stream;
|
|
42
|
+
}
|
|
43
|
+
static finish(incomingBody) {
|
|
44
|
+
if (incomingBody.#finished) {
|
|
45
|
+
throw new Error('incoming body already finished');
|
|
46
|
+
}
|
|
47
|
+
incomingBody.#finished = true;
|
|
48
|
+
return futureTrailersCreate();
|
|
49
|
+
}
|
|
50
|
+
[symbolDispose]() {}
|
|
51
|
+
static _create(streamId) {
|
|
52
|
+
const incomingBody = new IncomingBody();
|
|
53
|
+
incomingBody.#stream = inputStreamCreate(HTTP, streamId);
|
|
54
|
+
return incomingBody;
|
|
55
|
+
}
|
|
53
56
|
}
|
|
54
57
|
const incomingBodyCreate = IncomingBody._create;
|
|
55
58
|
delete IncomingBody._create;
|
|
56
59
|
|
|
57
60
|
class IncomingRequest {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
61
|
+
#method;
|
|
62
|
+
#pathWithQuery;
|
|
63
|
+
#scheme;
|
|
64
|
+
#authority;
|
|
65
|
+
#headers;
|
|
66
|
+
#streamId;
|
|
67
|
+
method() {
|
|
68
|
+
return this.#method;
|
|
69
|
+
}
|
|
70
|
+
pathWithQuery() {
|
|
71
|
+
return this.#pathWithQuery;
|
|
72
|
+
}
|
|
73
|
+
scheme() {
|
|
74
|
+
return this.#scheme;
|
|
75
|
+
}
|
|
76
|
+
authority() {
|
|
77
|
+
return this.#authority;
|
|
78
|
+
}
|
|
79
|
+
headers() {
|
|
80
|
+
return this.#headers;
|
|
81
|
+
}
|
|
82
|
+
consume() {
|
|
83
|
+
return incomingBodyCreate(this.#streamId);
|
|
84
|
+
}
|
|
85
|
+
[symbolDispose]() {}
|
|
86
|
+
static _create(
|
|
87
|
+
method,
|
|
88
|
+
pathWithQuery,
|
|
89
|
+
scheme,
|
|
90
|
+
authority,
|
|
91
|
+
headers,
|
|
92
|
+
streamId
|
|
93
|
+
) {
|
|
94
|
+
const incomingRequest = new IncomingRequest();
|
|
95
|
+
incomingRequest.#method = method;
|
|
96
|
+
incomingRequest.#pathWithQuery = pathWithQuery;
|
|
97
|
+
incomingRequest.#scheme = scheme;
|
|
98
|
+
incomingRequest.#authority = authority;
|
|
99
|
+
incomingRequest.#headers = headers;
|
|
100
|
+
incomingRequest.#streamId = streamId;
|
|
101
|
+
return incomingRequest;
|
|
102
|
+
}
|
|
93
103
|
}
|
|
94
104
|
const incomingRequestCreate = IncomingRequest._create;
|
|
95
105
|
delete IncomingRequest._create;
|
|
96
106
|
|
|
97
107
|
class FutureTrailers {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
108
|
+
#requested = false;
|
|
109
|
+
subscribe() {
|
|
110
|
+
return pollableCreate(0, this);
|
|
111
|
+
}
|
|
112
|
+
get() {
|
|
113
|
+
if (this.#requested) {
|
|
114
|
+
return { tag: 'err' };
|
|
115
|
+
}
|
|
116
|
+
this.#requested = true;
|
|
117
|
+
return {
|
|
118
|
+
tag: 'ok',
|
|
119
|
+
val: {
|
|
120
|
+
tag: 'ok',
|
|
121
|
+
val: undefined,
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
static _create() {
|
|
126
|
+
const res = new FutureTrailers();
|
|
127
|
+
return res;
|
|
128
|
+
}
|
|
117
129
|
}
|
|
118
130
|
const futureTrailersCreate = FutureTrailers._create;
|
|
119
131
|
delete FutureTrailers._create;
|
|
120
132
|
|
|
121
133
|
class OutgoingResponse {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
134
|
+
#body;
|
|
135
|
+
/** @type {number} */ #statusCode = 200;
|
|
136
|
+
/** @type {Fields} */ #headers;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @param {number} statusCode
|
|
140
|
+
* @param {Fields} headers
|
|
141
|
+
*/
|
|
142
|
+
constructor(headers) {
|
|
143
|
+
fieldsLock(headers);
|
|
144
|
+
this.#headers = headers;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
statusCode() {
|
|
148
|
+
return this.#statusCode;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
setStatusCode(statusCode) {
|
|
152
|
+
this.#statusCode = statusCode;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
headers() {
|
|
156
|
+
return this.#headers;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
body() {
|
|
160
|
+
let contentLengthValues = this.#headers.get('content-length');
|
|
161
|
+
let contentLength;
|
|
162
|
+
if (contentLengthValues.length > 0) {
|
|
163
|
+
contentLength = Number(
|
|
164
|
+
new TextDecoder().decode(contentLengthValues[0])
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
this.#body = outgoingBodyCreate(contentLength);
|
|
168
|
+
return this.#body;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
static _body(outgoingResponse) {
|
|
172
|
+
return outgoingResponse.#body;
|
|
173
|
+
}
|
|
159
174
|
}
|
|
160
175
|
|
|
161
176
|
const outgoingResponseBody = OutgoingResponse._body;
|
|
162
177
|
delete OutgoingResponse._body;
|
|
163
178
|
|
|
164
179
|
class ResponseOutparam {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
180
|
+
#setListener;
|
|
181
|
+
static set(param, response) {
|
|
182
|
+
param.#setListener(response);
|
|
183
|
+
}
|
|
184
|
+
static _create(setListener) {
|
|
185
|
+
const responseOutparam = new ResponseOutparam();
|
|
186
|
+
responseOutparam.#setListener = setListener;
|
|
187
|
+
return responseOutparam;
|
|
188
|
+
}
|
|
174
189
|
}
|
|
175
190
|
const responseOutparamCreate = ResponseOutparam._create;
|
|
176
191
|
delete ResponseOutparam._create;
|
|
@@ -178,186 +193,201 @@ delete ResponseOutparam._create;
|
|
|
178
193
|
const defaultHttpTimeout = 600_000_000_000n;
|
|
179
194
|
|
|
180
195
|
class RequestOptions {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
196
|
+
#connectTimeout = defaultHttpTimeout;
|
|
197
|
+
#firstByteTimeout = defaultHttpTimeout;
|
|
198
|
+
#betweenBytesTimeout = defaultHttpTimeout;
|
|
199
|
+
connectTimeout() {
|
|
200
|
+
return this.#connectTimeout;
|
|
201
|
+
}
|
|
202
|
+
setConnectTimeout(duration) {
|
|
203
|
+
this.#connectTimeout = duration;
|
|
204
|
+
}
|
|
205
|
+
firstByteTimeout() {
|
|
206
|
+
return this.#firstByteTimeout;
|
|
207
|
+
}
|
|
208
|
+
setFirstByteTimeout(duration) {
|
|
209
|
+
this.#firstByteTimeout = duration;
|
|
210
|
+
}
|
|
211
|
+
betweenBytesTimeout() {
|
|
212
|
+
return this.#betweenBytesTimeout;
|
|
213
|
+
}
|
|
214
|
+
setBetweenBytesTimeout(duration) {
|
|
215
|
+
this.#betweenBytesTimeout = duration;
|
|
216
|
+
}
|
|
202
217
|
}
|
|
203
218
|
|
|
204
219
|
class OutgoingRequest {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
220
|
+
/** @type {Method} */ #method = { tag: 'get' };
|
|
221
|
+
/** @type {Scheme | undefined} */ #scheme = undefined;
|
|
222
|
+
/** @type {string | undefined} */ #pathWithQuery = undefined;
|
|
223
|
+
/** @type {string | undefined} */ #authority = undefined;
|
|
224
|
+
/** @type {Fields} */ #headers;
|
|
225
|
+
/** @type {OutgoingBody} */ #body;
|
|
226
|
+
#bodyRequested = false;
|
|
227
|
+
constructor(headers) {
|
|
228
|
+
fieldsLock(headers);
|
|
229
|
+
this.#headers = headers;
|
|
230
|
+
let contentLengthValues = this.#headers.get('content-length');
|
|
231
|
+
if (contentLengthValues.length === 0) {
|
|
232
|
+
contentLengthValues = this.#headers.get('Content-Length');
|
|
233
|
+
}
|
|
234
|
+
let contentLength;
|
|
235
|
+
if (contentLengthValues.length > 0) {
|
|
236
|
+
contentLength = Number(
|
|
237
|
+
new TextDecoder().decode(contentLengthValues[0])
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
this.#body = outgoingBodyCreate(contentLength);
|
|
241
|
+
}
|
|
242
|
+
body() {
|
|
243
|
+
if (this.#bodyRequested) {
|
|
244
|
+
throw new Error('Body already requested');
|
|
245
|
+
}
|
|
246
|
+
this.#bodyRequested = true;
|
|
247
|
+
return this.#body;
|
|
248
|
+
}
|
|
249
|
+
method() {
|
|
250
|
+
return this.#method;
|
|
251
|
+
}
|
|
252
|
+
setMethod(method) {
|
|
253
|
+
if (method.tag === 'other' && !method.val.match(/^[a-zA-Z-]+$/)) {
|
|
254
|
+
throw undefined;
|
|
255
|
+
}
|
|
256
|
+
this.#method = method;
|
|
257
|
+
}
|
|
258
|
+
pathWithQuery() {
|
|
259
|
+
return this.#pathWithQuery;
|
|
260
|
+
}
|
|
261
|
+
setPathWithQuery(pathWithQuery) {
|
|
262
|
+
if (
|
|
263
|
+
pathWithQuery &&
|
|
264
|
+
!pathWithQuery.match(/^[a-zA-Z0-9.\-_~!$&'()*+,;=:@%?/]+$/)
|
|
265
|
+
) {
|
|
266
|
+
throw undefined;
|
|
267
|
+
}
|
|
268
|
+
this.#pathWithQuery = pathWithQuery;
|
|
269
|
+
}
|
|
270
|
+
scheme() {
|
|
271
|
+
return this.#scheme;
|
|
272
|
+
}
|
|
273
|
+
setScheme(scheme) {
|
|
274
|
+
if (scheme?.tag === 'other' && !scheme.val.match(/^[a-zA-Z]+$/)) {
|
|
275
|
+
throw undefined;
|
|
276
|
+
}
|
|
277
|
+
this.#scheme = scheme;
|
|
278
|
+
}
|
|
279
|
+
authority() {
|
|
280
|
+
return this.#authority;
|
|
281
|
+
}
|
|
282
|
+
setAuthority(authority) {
|
|
283
|
+
if (authority) {
|
|
284
|
+
const [host, port, ...extra] = authority.split(':');
|
|
285
|
+
const portNum = Number(port);
|
|
286
|
+
if (
|
|
287
|
+
extra.length ||
|
|
288
|
+
(port !== undefined &&
|
|
289
|
+
(portNum.toString() !== port || portNum > 65535)) ||
|
|
290
|
+
!host.match(/^[a-zA-Z0-9-.]+$/)
|
|
291
|
+
) {
|
|
292
|
+
throw undefined;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
this.#authority = authority;
|
|
296
|
+
}
|
|
297
|
+
headers() {
|
|
298
|
+
return this.#headers;
|
|
299
|
+
}
|
|
300
|
+
[symbolDispose]() {}
|
|
301
|
+
static _handle(request, options) {
|
|
302
|
+
const connectTimeout = options?.connectTimeout();
|
|
303
|
+
const betweenBytesTimeout = options?.betweenBytesTimeout();
|
|
304
|
+
const firstByteTimeout = options?.firstByteTimeout();
|
|
305
|
+
const scheme = schemeString(request.#scheme);
|
|
306
|
+
// note: host header is automatically added by Node.js
|
|
307
|
+
const headers = [];
|
|
308
|
+
const decoder = new TextDecoder();
|
|
309
|
+
for (const [key, value] of request.#headers.entries()) {
|
|
310
|
+
headers.push([key, decoder.decode(value)]);
|
|
311
|
+
}
|
|
312
|
+
if (!request.#pathWithQuery) {
|
|
313
|
+
throw { tag: 'HTTP-request-URI-invalid' };
|
|
314
|
+
}
|
|
315
|
+
return futureIncomingResponseCreate(
|
|
316
|
+
request.#method.val || request.#method.tag,
|
|
317
|
+
scheme,
|
|
318
|
+
request.#authority,
|
|
319
|
+
request.#pathWithQuery,
|
|
320
|
+
headers,
|
|
321
|
+
outgoingBodyOutputStreamId(request.#body),
|
|
322
|
+
connectTimeout,
|
|
323
|
+
betweenBytesTimeout,
|
|
324
|
+
firstByteTimeout
|
|
325
|
+
);
|
|
326
|
+
}
|
|
301
327
|
}
|
|
302
328
|
|
|
303
329
|
const outgoingRequestHandle = OutgoingRequest._handle;
|
|
304
330
|
delete OutgoingRequest._handle;
|
|
305
331
|
|
|
306
332
|
class OutgoingBody {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
333
|
+
#outputStream = null;
|
|
334
|
+
#outputStreamId = null;
|
|
335
|
+
#contentLength = undefined;
|
|
336
|
+
#finalizer;
|
|
337
|
+
write() {
|
|
338
|
+
// can only call write once
|
|
339
|
+
const outputStream = this.#outputStream;
|
|
340
|
+
if (outputStream === null) {
|
|
341
|
+
throw undefined;
|
|
342
|
+
}
|
|
343
|
+
this.#outputStream = null;
|
|
344
|
+
return outputStream;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* @param {OutgoingBody} body
|
|
348
|
+
* @param {Fields | undefined} trailers
|
|
349
|
+
*/
|
|
350
|
+
static finish(body, trailers) {
|
|
351
|
+
if (trailers) {
|
|
352
|
+
throw { tag: 'internal-error', val: 'trailers unsupported' };
|
|
353
|
+
}
|
|
354
|
+
// this will verify content length, and also verify not already finished
|
|
355
|
+
// throwing errors as appropriate
|
|
356
|
+
ioCall(HTTP_OUTPUT_STREAM_FINISH, body.#outputStreamId, null);
|
|
357
|
+
}
|
|
358
|
+
static _outputStreamId(outgoingBody) {
|
|
359
|
+
return outgoingBody.#outputStreamId;
|
|
360
|
+
}
|
|
361
|
+
static _create(contentLength) {
|
|
362
|
+
const outgoingBody = new OutgoingBody();
|
|
363
|
+
outgoingBody.#contentLength = contentLength;
|
|
364
|
+
outgoingBody.#outputStreamId = ioCall(
|
|
365
|
+
OUTPUT_STREAM_CREATE | HTTP,
|
|
366
|
+
null,
|
|
367
|
+
outgoingBody.#contentLength
|
|
368
|
+
);
|
|
369
|
+
outgoingBody.#outputStream = outputStreamCreate(
|
|
370
|
+
HTTP,
|
|
371
|
+
outgoingBody.#outputStreamId
|
|
372
|
+
);
|
|
373
|
+
outgoingBody.#finalizer = registerDispose(
|
|
374
|
+
outgoingBody,
|
|
375
|
+
null,
|
|
376
|
+
outgoingBody.#outputStreamId,
|
|
377
|
+
outgoingBodyDispose
|
|
378
|
+
);
|
|
379
|
+
return outgoingBody;
|
|
380
|
+
}
|
|
381
|
+
[symbolDispose]() {
|
|
382
|
+
if (this.#finalizer) {
|
|
383
|
+
earlyDispose(this.#finalizer);
|
|
384
|
+
this.#finalizer = null;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
357
387
|
}
|
|
358
388
|
|
|
359
389
|
function outgoingBodyDispose(id) {
|
|
360
|
-
|
|
390
|
+
ioCall(HTTP_OUTGOING_BODY_DISPOSE, id, null);
|
|
361
391
|
}
|
|
362
392
|
|
|
363
393
|
const outgoingBodyOutputStreamId = OutgoingBody._outputStreamId;
|
|
@@ -367,218 +397,237 @@ const outgoingBodyCreate = OutgoingBody._create;
|
|
|
367
397
|
delete OutgoingBody._create;
|
|
368
398
|
|
|
369
399
|
class IncomingResponse {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
400
|
+
/** @type {Fields} */ #headers = undefined;
|
|
401
|
+
#status = 0;
|
|
402
|
+
/** @type {number} */ #bodyStream;
|
|
403
|
+
status() {
|
|
404
|
+
return this.#status;
|
|
405
|
+
}
|
|
406
|
+
headers() {
|
|
407
|
+
return this.#headers;
|
|
408
|
+
}
|
|
409
|
+
consume() {
|
|
410
|
+
if (this.#bodyStream === undefined) {
|
|
411
|
+
throw undefined;
|
|
412
|
+
}
|
|
413
|
+
const bodyStream = this.#bodyStream;
|
|
414
|
+
this.#bodyStream = undefined;
|
|
415
|
+
return bodyStream;
|
|
416
|
+
}
|
|
417
|
+
[symbolDispose]() {
|
|
418
|
+
if (this.#bodyStream) {
|
|
419
|
+
this.#bodyStream[symbolDispose]();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
static _create(status, headers, bodyStreamId) {
|
|
423
|
+
const res = new IncomingResponse();
|
|
424
|
+
res.#status = status;
|
|
425
|
+
res.#headers = headers;
|
|
426
|
+
res.#bodyStream = incomingBodyCreate(bodyStreamId);
|
|
427
|
+
return res;
|
|
428
|
+
}
|
|
395
429
|
}
|
|
396
430
|
|
|
397
431
|
const incomingResponseCreate = IncomingResponse._create;
|
|
398
432
|
delete IncomingResponse._create;
|
|
399
433
|
|
|
400
434
|
class FutureIncomingResponse {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
435
|
+
#id;
|
|
436
|
+
#finalizer;
|
|
437
|
+
subscribe() {
|
|
438
|
+
return pollableCreate(
|
|
439
|
+
ioCall(FUTURE_SUBSCRIBE | HTTP, this.#id, null),
|
|
440
|
+
this
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
get() {
|
|
444
|
+
const ret = ioCall(FUTURE_TAKE_VALUE | HTTP, this.#id, null);
|
|
445
|
+
if (ret === undefined) {
|
|
446
|
+
return undefined;
|
|
447
|
+
}
|
|
448
|
+
if (ret.tag === 'ok' && ret.val.tag === 'ok') {
|
|
449
|
+
const textEncoder = new TextEncoder();
|
|
450
|
+
const { status, headers, bodyStreamId } = ret.val.val;
|
|
451
|
+
ret.val.val = incomingResponseCreate(
|
|
452
|
+
status,
|
|
453
|
+
fieldsFromEntriesChecked(
|
|
454
|
+
headers.map(([key, val]) => [key, textEncoder.encode(val)])
|
|
455
|
+
),
|
|
456
|
+
bodyStreamId
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
return ret;
|
|
460
|
+
}
|
|
461
|
+
static _create(
|
|
462
|
+
method,
|
|
463
|
+
scheme,
|
|
464
|
+
authority,
|
|
465
|
+
pathWithQuery,
|
|
466
|
+
headers,
|
|
467
|
+
body,
|
|
468
|
+
connectTimeout,
|
|
469
|
+
betweenBytesTimeout,
|
|
470
|
+
firstByteTimeout
|
|
471
|
+
) {
|
|
472
|
+
const res = new FutureIncomingResponse();
|
|
473
|
+
res.#id = ioCall(HTTP_CREATE_REQUEST, null, {
|
|
474
|
+
method,
|
|
475
|
+
scheme,
|
|
476
|
+
authority,
|
|
477
|
+
pathWithQuery,
|
|
478
|
+
headers,
|
|
479
|
+
body,
|
|
480
|
+
connectTimeout,
|
|
481
|
+
betweenBytesTimeout,
|
|
482
|
+
firstByteTimeout,
|
|
483
|
+
});
|
|
484
|
+
res.#finalizer = registerDispose(
|
|
485
|
+
res,
|
|
486
|
+
null,
|
|
487
|
+
res.#id,
|
|
488
|
+
futureIncomingResponseDispose
|
|
489
|
+
);
|
|
490
|
+
return res;
|
|
491
|
+
}
|
|
492
|
+
[symbolDispose]() {
|
|
493
|
+
if (this.#finalizer) {
|
|
494
|
+
earlyDispose(this.#finalizer);
|
|
495
|
+
this.#finalizer = null;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
462
498
|
}
|
|
463
499
|
|
|
464
500
|
function futureIncomingResponseDispose(id) {
|
|
465
|
-
|
|
501
|
+
ioCall(FUTURE_DISPOSE | HTTP, id, null);
|
|
466
502
|
}
|
|
467
503
|
|
|
468
504
|
const futureIncomingResponseCreate = FutureIncomingResponse._create;
|
|
469
505
|
delete FutureIncomingResponse._create;
|
|
470
506
|
|
|
471
507
|
class Fields {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
(
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
fields.#
|
|
578
|
-
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
|
|
508
|
+
#immutable = false;
|
|
509
|
+
/** @type {[string, Uint8Array[]][]} */ #entries = [];
|
|
510
|
+
/** @type {Map<string, [string, Uint8Array[]][]>} */ #table = new Map();
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* @param {[string, Uint8Array[][]][]} entries
|
|
514
|
+
*/
|
|
515
|
+
static fromList(entries) {
|
|
516
|
+
const fields = new Fields();
|
|
517
|
+
for (const [key, value] of entries) {
|
|
518
|
+
fields.append(key, value);
|
|
519
|
+
}
|
|
520
|
+
return fields;
|
|
521
|
+
}
|
|
522
|
+
get(name) {
|
|
523
|
+
const tableEntries = this.#table.get(name.toLowerCase());
|
|
524
|
+
if (!tableEntries) {
|
|
525
|
+
return [];
|
|
526
|
+
}
|
|
527
|
+
return tableEntries.map(([, v]) => v);
|
|
528
|
+
}
|
|
529
|
+
set(name, values) {
|
|
530
|
+
if (this.#immutable) {
|
|
531
|
+
throw { tag: 'immutable' };
|
|
532
|
+
}
|
|
533
|
+
try {
|
|
534
|
+
validateHeaderName(name);
|
|
535
|
+
} catch {
|
|
536
|
+
throw { tag: 'invalid-syntax' };
|
|
537
|
+
}
|
|
538
|
+
for (const value of values) {
|
|
539
|
+
try {
|
|
540
|
+
validateHeaderValue(name, new TextDecoder().decode(value));
|
|
541
|
+
} catch {
|
|
542
|
+
throw { tag: 'invalid-syntax' };
|
|
543
|
+
}
|
|
544
|
+
throw { tag: 'invalid-syntax' };
|
|
545
|
+
}
|
|
546
|
+
const lowercased = name.toLowerCase();
|
|
547
|
+
if (_forbiddenHeaders.has(lowercased)) {
|
|
548
|
+
throw { tag: 'forbidden' };
|
|
549
|
+
}
|
|
550
|
+
const tableEntries = this.#table.get(lowercased);
|
|
551
|
+
if (tableEntries) {
|
|
552
|
+
this.#entries = this.#entries.filter(
|
|
553
|
+
(entry) => !tableEntries.includes(entry)
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
tableEntries.splice(0, tableEntries.length);
|
|
557
|
+
for (const value of values) {
|
|
558
|
+
const entry = [name, value];
|
|
559
|
+
this.#entries.push(entry);
|
|
560
|
+
tableEntries.push(entry);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
has(name) {
|
|
564
|
+
return this.#table.has(name.toLowerCase());
|
|
565
|
+
}
|
|
566
|
+
delete(name) {
|
|
567
|
+
if (this.#immutable) {
|
|
568
|
+
throw { tag: 'immutable' };
|
|
569
|
+
}
|
|
570
|
+
const lowercased = name.toLowerCase();
|
|
571
|
+
const tableEntries = this.#table.get(lowercased);
|
|
572
|
+
if (tableEntries) {
|
|
573
|
+
this.#entries = this.#entries.filter(
|
|
574
|
+
(entry) => !tableEntries.includes(entry)
|
|
575
|
+
);
|
|
576
|
+
this.#table.delete(lowercased);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
append(name, value) {
|
|
580
|
+
if (this.#immutable) {
|
|
581
|
+
throw { tag: 'immutable' };
|
|
582
|
+
}
|
|
583
|
+
try {
|
|
584
|
+
validateHeaderName(name);
|
|
585
|
+
} catch {
|
|
586
|
+
throw { tag: 'invalid-syntax' };
|
|
587
|
+
}
|
|
588
|
+
try {
|
|
589
|
+
validateHeaderValue(name, new TextDecoder().decode(value));
|
|
590
|
+
} catch {
|
|
591
|
+
throw { tag: 'invalid-syntax' };
|
|
592
|
+
}
|
|
593
|
+
const lowercased = name.toLowerCase();
|
|
594
|
+
if (_forbiddenHeaders.has(lowercased)) {
|
|
595
|
+
throw { tag: 'forbidden' };
|
|
596
|
+
}
|
|
597
|
+
const entry = [name, value];
|
|
598
|
+
this.#entries.push(entry);
|
|
599
|
+
const tableEntries = this.#table.get(lowercased);
|
|
600
|
+
if (tableEntries) {
|
|
601
|
+
tableEntries.push(entry);
|
|
602
|
+
} else {
|
|
603
|
+
this.#table.set(lowercased, [entry]);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
entries() {
|
|
607
|
+
return this.#entries;
|
|
608
|
+
}
|
|
609
|
+
clone() {
|
|
610
|
+
return fieldsFromEntriesChecked(this.#entries);
|
|
611
|
+
}
|
|
612
|
+
static _lock(fields) {
|
|
613
|
+
fields.#immutable = true;
|
|
614
|
+
return fields;
|
|
615
|
+
}
|
|
616
|
+
// assumes entries are already validated
|
|
617
|
+
static _fromEntriesChecked(entries) {
|
|
618
|
+
const fields = new Fields();
|
|
619
|
+
fields.#entries = entries;
|
|
620
|
+
for (const entry of entries) {
|
|
621
|
+
const lowercase = entry[0].toLowerCase();
|
|
622
|
+
const existing = fields.#table.get(lowercase);
|
|
623
|
+
if (existing) {
|
|
624
|
+
existing.push(entry);
|
|
625
|
+
} else {
|
|
626
|
+
fields.#table.set(lowercase, [entry]);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return fields;
|
|
630
|
+
}
|
|
582
631
|
}
|
|
583
632
|
const fieldsLock = Fields._lock;
|
|
584
633
|
delete Fields._lock;
|
|
@@ -586,130 +635,157 @@ const fieldsFromEntriesChecked = Fields._fromEntriesChecked;
|
|
|
586
635
|
delete Fields._fromEntriesChecked;
|
|
587
636
|
|
|
588
637
|
export const outgoingHandler = {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
638
|
+
/**
|
|
639
|
+
* @param {OutgoingRequest} request
|
|
640
|
+
* @param {RequestOptions | undefined} options
|
|
641
|
+
* @returns {FutureIncomingResponse}
|
|
642
|
+
*/
|
|
643
|
+
handle: outgoingRequestHandle,
|
|
595
644
|
};
|
|
596
645
|
|
|
597
646
|
function httpErrorCode(err) {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
647
|
+
if (err.payload) {
|
|
648
|
+
return err.payload;
|
|
649
|
+
}
|
|
650
|
+
return {
|
|
651
|
+
tag: 'internal-error',
|
|
652
|
+
val: err.message,
|
|
653
|
+
};
|
|
603
654
|
}
|
|
604
655
|
|
|
605
656
|
export const types = {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
657
|
+
Fields,
|
|
658
|
+
FutureIncomingResponse,
|
|
659
|
+
FutureTrailers,
|
|
660
|
+
IncomingBody,
|
|
661
|
+
IncomingRequest,
|
|
662
|
+
IncomingResponse,
|
|
663
|
+
OutgoingBody,
|
|
664
|
+
OutgoingRequest,
|
|
665
|
+
OutgoingResponse,
|
|
666
|
+
ResponseOutparam,
|
|
667
|
+
RequestOptions,
|
|
668
|
+
httpErrorCode,
|
|
618
669
|
};
|
|
619
670
|
|
|
620
671
|
function schemeString(scheme) {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
672
|
+
if (!scheme) {
|
|
673
|
+
return 'https:';
|
|
674
|
+
}
|
|
675
|
+
switch (scheme.tag) {
|
|
676
|
+
case 'HTTP':
|
|
677
|
+
return 'http:';
|
|
678
|
+
case 'HTTPS':
|
|
679
|
+
return 'https:';
|
|
680
|
+
case 'other':
|
|
681
|
+
return scheme.val.toLowerCase() + ':';
|
|
682
|
+
}
|
|
630
683
|
}
|
|
631
684
|
|
|
632
685
|
const supportedMethods = [
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
686
|
+
'get',
|
|
687
|
+
'head',
|
|
688
|
+
'post',
|
|
689
|
+
'put',
|
|
690
|
+
'delete',
|
|
691
|
+
'connect',
|
|
692
|
+
'options',
|
|
693
|
+
'trace',
|
|
694
|
+
'patch',
|
|
642
695
|
];
|
|
643
696
|
function parseMethod(method) {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
697
|
+
const lowercase = method.toLowerCase();
|
|
698
|
+
if (supportedMethods.includes(method.toLowerCase())) {
|
|
699
|
+
return { tag: lowercase };
|
|
700
|
+
}
|
|
701
|
+
return { tag: 'other', val: lowercase };
|
|
648
702
|
}
|
|
649
703
|
|
|
650
704
|
const httpServers = new Map();
|
|
651
705
|
let httpServerCnt = 0;
|
|
652
706
|
export class HTTPServer {
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
}
|
|
661
|
-
registerIncomingHttpHandler(
|
|
662
|
-
this.#id,
|
|
663
|
-
({ method, pathWithQuery, host, headers, responseId, streamId }) => {
|
|
664
|
-
const textEncoder = new TextEncoder();
|
|
665
|
-
const request = incomingRequestCreate(
|
|
666
|
-
parseMethod(method),
|
|
667
|
-
pathWithQuery,
|
|
668
|
-
{ tag: "HTTP" },
|
|
669
|
-
host,
|
|
670
|
-
fieldsLock(
|
|
671
|
-
fieldsFromEntriesChecked(
|
|
672
|
-
headers
|
|
673
|
-
.filter(([key]) => !_forbiddenHeaders.has(key))
|
|
674
|
-
.map(([key, val]) => [key, textEncoder.encode(val)])
|
|
675
|
-
)
|
|
676
|
-
),
|
|
677
|
-
streamId
|
|
678
|
-
);
|
|
679
|
-
let outgoingBodyStreamId;
|
|
680
|
-
const responseOutparam = responseOutparamCreate((response) => {
|
|
681
|
-
if (response.tag === "ok") {
|
|
682
|
-
const outgoingResponse = response.val;
|
|
683
|
-
const statusCode = outgoingResponse.statusCode();
|
|
684
|
-
const headers = outgoingResponse.headers().entries();
|
|
685
|
-
const body = outgoingResponseBody(outgoingResponse);
|
|
686
|
-
outgoingBodyStreamId = outgoingBodyOutputStreamId(body);
|
|
687
|
-
ioCall(HTTP_SERVER_SET_OUTGOING_RESPONSE, responseId, {
|
|
688
|
-
statusCode,
|
|
689
|
-
headers,
|
|
690
|
-
streamId: outgoingBodyStreamId,
|
|
691
|
-
});
|
|
692
|
-
} else {
|
|
693
|
-
ioCall(HTTP_SERVER_CLEAR_OUTGOING_RESPONSE, responseId, null);
|
|
694
|
-
console.error(response.val);
|
|
707
|
+
#id = ++httpServerCnt;
|
|
708
|
+
#stopped = false;
|
|
709
|
+
#liveEventLoopInterval;
|
|
710
|
+
constructor(incomingHandler) {
|
|
711
|
+
httpServers.set(this.#id, this);
|
|
712
|
+
if (typeof incomingHandler?.handle !== 'function') {
|
|
713
|
+
console.error('Not a valid HTTP server component to execute.');
|
|
695
714
|
process.exit(1);
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
+
}
|
|
716
|
+
registerIncomingHttpHandler(
|
|
717
|
+
this.#id,
|
|
718
|
+
({
|
|
719
|
+
method,
|
|
720
|
+
pathWithQuery,
|
|
721
|
+
host,
|
|
722
|
+
headers,
|
|
723
|
+
responseId,
|
|
724
|
+
streamId,
|
|
725
|
+
}) => {
|
|
726
|
+
const textEncoder = new TextEncoder();
|
|
727
|
+
const request = incomingRequestCreate(
|
|
728
|
+
parseMethod(method),
|
|
729
|
+
pathWithQuery,
|
|
730
|
+
{ tag: 'HTTP' },
|
|
731
|
+
host,
|
|
732
|
+
fieldsLock(
|
|
733
|
+
fieldsFromEntriesChecked(
|
|
734
|
+
headers
|
|
735
|
+
.filter(([key]) => !_forbiddenHeaders.has(key))
|
|
736
|
+
.map(([key, val]) => [
|
|
737
|
+
key,
|
|
738
|
+
textEncoder.encode(val),
|
|
739
|
+
])
|
|
740
|
+
)
|
|
741
|
+
),
|
|
742
|
+
streamId
|
|
743
|
+
);
|
|
744
|
+
let outgoingBodyStreamId;
|
|
745
|
+
const responseOutparam = responseOutparamCreate((response) => {
|
|
746
|
+
if (response.tag === 'ok') {
|
|
747
|
+
const outgoingResponse = response.val;
|
|
748
|
+
const statusCode = outgoingResponse.statusCode();
|
|
749
|
+
const headers = outgoingResponse.headers().entries();
|
|
750
|
+
const body = outgoingResponseBody(outgoingResponse);
|
|
751
|
+
outgoingBodyStreamId = outgoingBodyOutputStreamId(body);
|
|
752
|
+
ioCall(HTTP_SERVER_SET_OUTGOING_RESPONSE, responseId, {
|
|
753
|
+
statusCode,
|
|
754
|
+
headers,
|
|
755
|
+
streamId: outgoingBodyStreamId,
|
|
756
|
+
});
|
|
757
|
+
} else {
|
|
758
|
+
ioCall(
|
|
759
|
+
HTTP_SERVER_CLEAR_OUTGOING_RESPONSE,
|
|
760
|
+
responseId,
|
|
761
|
+
null
|
|
762
|
+
);
|
|
763
|
+
console.error(response.val);
|
|
764
|
+
process.exit(1);
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
incomingHandler.handle(request, responseOutparam);
|
|
768
|
+
if (outgoingBodyStreamId) {
|
|
769
|
+
ioCall(OUTPUT_STREAM_DISPOSE, outgoingBodyStreamId, null);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
listen(port, host) {
|
|
775
|
+
// set a dummy interval, to keep the process alive since the server is off-thread
|
|
776
|
+
this.#liveEventLoopInterval = setInterval(() => {}, 10_000);
|
|
777
|
+
ioCall(HTTP_SERVER_START, this.#id, { port, host });
|
|
778
|
+
}
|
|
779
|
+
stop() {
|
|
780
|
+
if (this.#stopped) {
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
clearInterval(this.#liveEventLoopInterval);
|
|
784
|
+
ioCall(HTTP_SERVER_STOP, this.#id, null);
|
|
785
|
+
httpServers.delete(this.#id);
|
|
786
|
+
this.#stopped = true;
|
|
787
|
+
}
|
|
788
|
+
close() {
|
|
789
|
+
this.stop();
|
|
790
|
+
}
|
|
715
791
|
}
|