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