goscript 0.2.2 → 0.2.4
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/compiler/gotest/testdata/browserapi/browserapi_test.go +36 -0
- package/compiler/lowering.go +279 -16
- package/compiler/override-registry_test.go +175 -0
- package/compiler/protobuf-ts-binding.go +154 -6
- package/compiler/protobuf-ts-binding_test.go +7 -2
- package/compiler/runtime-contract.go +2 -0
- package/compiler/runtime-contract_test.go +1 -0
- package/compiler/semantic-model.go +16 -0
- package/compiler/semantic-model_test.go +38 -0
- package/compiler/skeleton_test.go +522 -17
- package/compiler/typescript-emitter.go +4 -0
- package/dist/gs/builtin/builtin.js +7 -9
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/defer.js +2 -2
- package/dist/gs/builtin/hostio.js +5 -5
- package/dist/gs/builtin/hostio.js.map +1 -1
- package/dist/gs/builtin/map.js +2 -1
- package/dist/gs/builtin/map.js.map +1 -1
- package/dist/gs/builtin/slice.d.ts +3 -0
- package/dist/gs/builtin/slice.js +39 -0
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/builtin/type.js +49 -0
- package/dist/gs/builtin/type.js.map +1 -1
- package/dist/gs/compress/gzip/index.d.ts +41 -0
- package/dist/gs/compress/gzip/index.js +235 -0
- package/dist/gs/compress/gzip/index.js.map +1 -0
- package/dist/gs/compress/zlib/index.js +5 -2
- package/dist/gs/compress/zlib/index.js.map +1 -1
- package/dist/gs/crypto/ecdh/index.js +27 -8
- package/dist/gs/crypto/ecdh/index.js.map +1 -1
- package/dist/gs/crypto/ed25519/index.js +3 -3
- package/dist/gs/crypto/ed25519/index.js.map +1 -1
- package/dist/gs/crypto/rand/index.js +6 -3
- package/dist/gs/crypto/rand/index.js.map +1 -1
- package/dist/gs/embed/index.js +9 -3
- package/dist/gs/embed/index.js.map +1 -1
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -0
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +33 -0
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
- package/dist/gs/github.com/mr-tron/base58/base58/index.js +4 -1
- package/dist/gs/github.com/mr-tron/base58/base58/index.js.map +1 -1
- package/dist/gs/golang.org/x/crypto/scrypt/index.d.ts +2 -0
- package/dist/gs/golang.org/x/crypto/scrypt/index.js +39 -0
- package/dist/gs/golang.org/x/crypto/scrypt/index.js.map +1 -0
- package/dist/gs/hash/fnv/index.js +13 -5
- package/dist/gs/hash/fnv/index.js.map +1 -1
- package/dist/gs/io/fs/glob.d.ts +3 -3
- package/dist/gs/io/fs/glob.js +9 -9
- package/dist/gs/io/fs/glob.js.map +1 -1
- package/dist/gs/io/fs/readdir.d.ts +2 -2
- package/dist/gs/io/fs/readdir.js +13 -74
- package/dist/gs/io/fs/readdir.js.map +1 -1
- package/dist/gs/io/fs/readlink.d.ts +1 -1
- package/dist/gs/io/fs/readlink.js +2 -2
- package/dist/gs/io/fs/readlink.js.map +1 -1
- package/dist/gs/io/fs/stat.d.ts +4 -2
- package/dist/gs/io/fs/stat.js +12 -73
- package/dist/gs/io/fs/stat.js.map +1 -1
- package/dist/gs/io/fs/sub.d.ts +2 -2
- package/dist/gs/io/fs/sub.js +11 -11
- package/dist/gs/io/fs/sub.js.map +1 -1
- package/dist/gs/io/fs/walk.js +2 -2
- package/dist/gs/io/fs/walk.js.map +1 -1
- package/dist/gs/maps/iter.js.map +1 -1
- package/dist/gs/maps/maps.js.map +1 -1
- package/dist/gs/mime/index.js +5 -2
- package/dist/gs/mime/index.js.map +1 -1
- package/dist/gs/net/http/httptest/index.js +6 -3
- package/dist/gs/net/http/httptest/index.js.map +1 -1
- package/dist/gs/net/http/index.d.ts +34 -18
- package/dist/gs/net/http/index.js +280 -63
- package/dist/gs/net/http/index.js.map +1 -1
- package/dist/gs/net/http/pprof/index.d.ts +5 -5
- package/dist/gs/net/http/pprof/index.js +21 -21
- package/dist/gs/net/http/pprof/index.js.map +1 -1
- package/dist/gs/reflect/iter.js +1 -1
- package/dist/gs/reflect/iter.js.map +1 -1
- package/dist/gs/reflect/type.d.ts +2 -0
- package/dist/gs/reflect/type.js +53 -21
- package/dist/gs/reflect/type.js.map +1 -1
- package/dist/gs/runtime/pprof/index.js.map +1 -1
- package/dist/gs/runtime/runtime.js +2 -2
- package/dist/gs/runtime/runtime.js.map +1 -1
- package/dist/gs/runtime/trace/index.js.map +1 -1
- package/dist/gs/slices/slices.d.ts +1 -1
- package/dist/gs/slices/slices.js +37 -4
- package/dist/gs/slices/slices.js.map +1 -1
- package/gs/builtin/builtin.ts +11 -14
- package/gs/builtin/defer.ts +2 -2
- package/gs/builtin/hostio.ts +5 -5
- package/gs/builtin/map.ts +4 -1
- package/gs/builtin/runtime-contract.test.ts +25 -0
- package/gs/builtin/slice.test.ts +14 -0
- package/gs/builtin/slice.ts +64 -0
- package/gs/builtin/type.ts +72 -0
- package/gs/bytes/bytes.test.ts +14 -13
- package/gs/compress/gzip/index.test.ts +86 -0
- package/gs/compress/gzip/index.ts +297 -0
- package/gs/compress/gzip/meta.json +6 -0
- package/gs/compress/gzip/parity.json +45 -0
- package/gs/compress/zlib/index.test.ts +19 -5
- package/gs/compress/zlib/index.ts +16 -7
- package/gs/context/context.test.ts +3 -1
- package/gs/crypto/ecdh/index.test.ts +6 -2
- package/gs/crypto/ecdh/index.ts +49 -12
- package/gs/crypto/ed25519/index.ts +20 -7
- package/gs/crypto/rand/index.ts +6 -3
- package/gs/embed/index.test.ts +4 -4
- package/gs/embed/index.ts +9 -3
- package/gs/fmt/fmt.test.ts +29 -4
- package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +126 -0
- package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +46 -0
- package/gs/github.com/mr-tron/base58/base58/index.ts +9 -3
- package/gs/github.com/zeebo/blake3/internal/consts/index.test.ts +2 -8
- package/gs/golang.org/x/crypto/scrypt/index.test.ts +81 -0
- package/gs/golang.org/x/crypto/scrypt/index.ts +54 -0
- package/gs/golang.org/x/crypto/scrypt/meta.json +5 -0
- package/gs/hash/fnv/index.test.ts +1 -8
- package/gs/hash/fnv/index.ts +27 -10
- package/gs/io/fs/glob.ts +14 -11
- package/gs/io/fs/meta.json +5 -0
- package/gs/io/fs/readdir.test.ts +63 -2
- package/gs/io/fs/readdir.ts +33 -30
- package/gs/io/fs/readlink.test.ts +2 -2
- package/gs/io/fs/readlink.ts +5 -2
- package/gs/io/fs/stat.test.ts +79 -0
- package/gs/io/fs/stat.ts +24 -10
- package/gs/io/fs/sub.test.ts +93 -0
- package/gs/io/fs/sub.ts +13 -13
- package/gs/io/fs/walk.ts +2 -2
- package/gs/maps/iter.ts +9 -9
- package/gs/maps/maps.ts +4 -4
- package/gs/math/bits/index.test.ts +10 -1
- package/gs/mime/index.test.ts +33 -15
- package/gs/mime/index.ts +9 -2
- package/gs/net/http/httptest/index.test.ts +17 -3
- package/gs/net/http/httptest/index.ts +8 -3
- package/gs/net/http/index.test.ts +851 -124
- package/gs/net/http/index.ts +612 -146
- package/gs/net/http/meta.json +3 -1
- package/gs/net/http/pprof/index.test.ts +4 -4
- package/gs/net/http/pprof/index.ts +43 -22
- package/gs/os/file_unix_js.test.ts +22 -0
- package/gs/reflect/iter.ts +4 -2
- package/gs/reflect/map.test.ts +56 -1
- package/gs/reflect/type.ts +76 -37
- package/gs/runtime/pprof/index.test.ts +7 -1
- package/gs/runtime/pprof/index.ts +5 -1
- package/gs/runtime/runtime.test.ts +7 -0
- package/gs/runtime/runtime.ts +2 -4
- package/gs/runtime/trace/index.test.ts +9 -1
- package/gs/runtime/trace/index.ts +5 -1
- package/gs/slices/meta.json +3 -0
- package/gs/slices/slices.test.ts +59 -21
- package/gs/slices/slices.ts +61 -20
- package/gs/strconv/complex.test.ts +17 -3
- package/gs/sync/atomic/doc_64.test.ts +2 -9
- package/gs/sync/sync.test.ts +18 -8
- package/gs/syscall/js/index.test.ts +9 -4
- package/package.json +5 -4
|
@@ -4,6 +4,8 @@ import * as context from '@goscript/context/index.js';
|
|
|
4
4
|
import * as errors from '@goscript/errors/index.js';
|
|
5
5
|
import * as fs from '@goscript/io/fs/fs.js';
|
|
6
6
|
import * as io from '@goscript/io/index.js';
|
|
7
|
+
import * as mime from '@goscript/mime/index.js';
|
|
8
|
+
import * as path from '@goscript/path/index.js';
|
|
7
9
|
import * as strings from '@goscript/strings/index.js';
|
|
8
10
|
import * as time from '@goscript/time/index.js';
|
|
9
11
|
export const StatusContinue = 100;
|
|
@@ -210,28 +212,39 @@ export const Header = Map;
|
|
|
210
212
|
export function CanonicalHeaderKey(s) {
|
|
211
213
|
return canonicalMIMEHeaderKey(s);
|
|
212
214
|
}
|
|
215
|
+
function headerMap(h) {
|
|
216
|
+
let value = $.pointerValue(h);
|
|
217
|
+
while (value !== null &&
|
|
218
|
+
value !== undefined &&
|
|
219
|
+
typeof value === 'object' &&
|
|
220
|
+
'__goValue' in value) {
|
|
221
|
+
value = $.pointerValue(value.__goValue);
|
|
222
|
+
}
|
|
223
|
+
return value;
|
|
224
|
+
}
|
|
213
225
|
export function Header_Add(h, key, value) {
|
|
226
|
+
const headers = headerMap(h);
|
|
214
227
|
key = canonicalMIMEHeaderKey(key);
|
|
215
|
-
const values = Array.from(
|
|
228
|
+
const values = Array.from(headers.get(key) ?? []);
|
|
216
229
|
values.push(value);
|
|
217
|
-
|
|
230
|
+
headers.set(key, $.arrayToSlice(values));
|
|
218
231
|
}
|
|
219
232
|
export function Header_Del(h, key) {
|
|
220
|
-
h.delete(canonicalMIMEHeaderKey(key));
|
|
233
|
+
headerMap(h).delete(canonicalMIMEHeaderKey(key));
|
|
221
234
|
}
|
|
222
235
|
export function Header_Get(h, key) {
|
|
223
|
-
const values = h.get(canonicalMIMEHeaderKey(key));
|
|
236
|
+
const values = headerMap(h).get(canonicalMIMEHeaderKey(key));
|
|
224
237
|
return values == null || values.length === 0 ? '' : String(values[0]);
|
|
225
238
|
}
|
|
226
239
|
export function Header_Set(h, key, value) {
|
|
227
|
-
h.set(canonicalMIMEHeaderKey(key), $.arrayToSlice([value]));
|
|
240
|
+
headerMap(h).set(canonicalMIMEHeaderKey(key), $.arrayToSlice([value]));
|
|
228
241
|
}
|
|
229
242
|
export function Header_Values(h, key) {
|
|
230
|
-
return h.get(canonicalMIMEHeaderKey(key)) ?? null;
|
|
243
|
+
return headerMap(h).get(canonicalMIMEHeaderKey(key)) ?? null;
|
|
231
244
|
}
|
|
232
245
|
export function Header_Clone(h) {
|
|
233
246
|
const cloned = new Header();
|
|
234
|
-
for (const [key, values] of h.entries()) {
|
|
247
|
+
for (const [key, values] of headerMap(h).entries()) {
|
|
235
248
|
cloned.set(key, $.arrayToSlice(Array.from(values ?? [])));
|
|
236
249
|
}
|
|
237
250
|
return cloned;
|
|
@@ -240,7 +253,7 @@ export function Header_Write(h, w) {
|
|
|
240
253
|
return Header_WriteSubset(h, w, null);
|
|
241
254
|
}
|
|
242
255
|
export function Header_WriteSubset(h, w, exclude) {
|
|
243
|
-
for (const [key, values] of h.entries()) {
|
|
256
|
+
for (const [key, values] of headerMap(h).entries()) {
|
|
244
257
|
if (exclude?.get(key) === true) {
|
|
245
258
|
continue;
|
|
246
259
|
}
|
|
@@ -324,7 +337,10 @@ function parseRequestURL(rawURL) {
|
|
|
324
337
|
}
|
|
325
338
|
const parsed = new URL(rawURL, 'http://goscript.invalid');
|
|
326
339
|
const hasHost = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(rawURL);
|
|
327
|
-
return [
|
|
340
|
+
return [
|
|
341
|
+
new RequestURL(parsed.pathname, parsed.search.startsWith('?') ? parsed.search.slice(1) : parsed.search, hasHost ? parsed.protocol.replace(/:$/, '') : '', hasHost ? parsed.host : ''),
|
|
342
|
+
null,
|
|
343
|
+
];
|
|
328
344
|
}
|
|
329
345
|
catch {
|
|
330
346
|
return [null, errors.New(`parse "${rawURL}": invalid URL`)];
|
|
@@ -342,6 +358,46 @@ class responseBody {
|
|
|
342
358
|
return null;
|
|
343
359
|
}
|
|
344
360
|
}
|
|
361
|
+
class fetchResponseBody {
|
|
362
|
+
fetched;
|
|
363
|
+
requestContext;
|
|
364
|
+
abortFetch;
|
|
365
|
+
stopContextWatch;
|
|
366
|
+
reader = null;
|
|
367
|
+
closed = false;
|
|
368
|
+
constructor(fetched, requestContext, abortFetch, stopContextWatch) {
|
|
369
|
+
this.fetched = fetched;
|
|
370
|
+
this.requestContext = requestContext;
|
|
371
|
+
this.abortFetch = abortFetch;
|
|
372
|
+
this.stopContextWatch = stopContextWatch;
|
|
373
|
+
}
|
|
374
|
+
async Read(p) {
|
|
375
|
+
if (this.closed) {
|
|
376
|
+
return [0, ErrBodyReadAfterClose];
|
|
377
|
+
}
|
|
378
|
+
if (this.reader == null) {
|
|
379
|
+
const [data, err] = await readFetchBody(this.fetched, this.requestContext, this.abortFetch);
|
|
380
|
+
if (err != null) {
|
|
381
|
+
this.stopContextWatch();
|
|
382
|
+
return [0, err];
|
|
383
|
+
}
|
|
384
|
+
this.stopContextWatch();
|
|
385
|
+
this.reader = bytes.NewReader(data);
|
|
386
|
+
}
|
|
387
|
+
return this.reader.Read(p);
|
|
388
|
+
}
|
|
389
|
+
Close() {
|
|
390
|
+
if (this.closed) {
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
this.closed = true;
|
|
394
|
+
this.stopContextWatch();
|
|
395
|
+
if (this.reader == null) {
|
|
396
|
+
this.abortFetch();
|
|
397
|
+
}
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
345
401
|
class noBody {
|
|
346
402
|
Read(_p) {
|
|
347
403
|
return [0, io.EOF];
|
|
@@ -386,7 +442,9 @@ export class Cookie {
|
|
|
386
442
|
this.Unparsed = init?.Unparsed ?? null;
|
|
387
443
|
}
|
|
388
444
|
String() {
|
|
389
|
-
const parts = [
|
|
445
|
+
const parts = [
|
|
446
|
+
`${this.Name}=${this.Quoted ? quoteCookieValue(this.Value) : this.Value}`,
|
|
447
|
+
];
|
|
390
448
|
if (this.Path !== '') {
|
|
391
449
|
parts.push(`Path=${this.Path}`);
|
|
392
450
|
}
|
|
@@ -429,12 +487,19 @@ function isToken(value) {
|
|
|
429
487
|
return /^[!#$%&'*+\-.^_`{}|~0-9A-Za-z]+$/.test(value);
|
|
430
488
|
}
|
|
431
489
|
function validCookieValueByte(code) {
|
|
432
|
-
return code >= 0x20 &&
|
|
490
|
+
return (code >= 0x20 &&
|
|
491
|
+
code < 0x7f &&
|
|
492
|
+
code !== 0x22 &&
|
|
493
|
+
code !== 0x3b &&
|
|
494
|
+
code !== 0x5c);
|
|
433
495
|
}
|
|
434
496
|
function parseCookieValue(raw, allowDoubleQuote) {
|
|
435
497
|
let value = raw;
|
|
436
498
|
let quoted = false;
|
|
437
|
-
if (allowDoubleQuote &&
|
|
499
|
+
if (allowDoubleQuote &&
|
|
500
|
+
value.length > 1 &&
|
|
501
|
+
value[0] === '"' &&
|
|
502
|
+
value[value.length - 1] === '"') {
|
|
438
503
|
value = value.slice(1, -1);
|
|
439
504
|
quoted = true;
|
|
440
505
|
}
|
|
@@ -453,12 +518,12 @@ function asciiLower(value) {
|
|
|
453
518
|
}
|
|
454
519
|
return [value.toLowerCase(), true];
|
|
455
520
|
}
|
|
456
|
-
export function SetCookie(w, cookie) {
|
|
521
|
+
export async function SetCookie(w, cookie) {
|
|
457
522
|
const c = $.pointerValue(cookie);
|
|
458
523
|
if (w == null || c == null) {
|
|
459
524
|
return;
|
|
460
525
|
}
|
|
461
|
-
Header_Add(w.Header(), 'Set-Cookie', c.String());
|
|
526
|
+
Header_Add(await w.Header(), 'Set-Cookie', c.String());
|
|
462
527
|
}
|
|
463
528
|
class memoryResponseWriter {
|
|
464
529
|
Code = StatusOK;
|
|
@@ -507,7 +572,7 @@ function inProcessServerRequest(request) {
|
|
|
507
572
|
const rawQuery = request.URL?.RawQuery ?? '';
|
|
508
573
|
const query = rawQuery === '' ? '' : `?${rawQuery}`;
|
|
509
574
|
req.RequestURI = `${request.URL?.Path ?? '/'}${query}`;
|
|
510
|
-
req.Host = request.Host === '' ? request.URL?.Host ?? '' : request.Host;
|
|
575
|
+
req.Host = request.Host === '' ? (request.URL?.Host ?? '') : request.Host;
|
|
511
576
|
if (req.URL?.clone != null) {
|
|
512
577
|
req.URL = req.URL.clone();
|
|
513
578
|
req.URL.Scheme = '';
|
|
@@ -560,7 +625,9 @@ export class Request {
|
|
|
560
625
|
this.Cancel = init?.Cancel ?? null;
|
|
561
626
|
this.Response = init?.Response ?? null;
|
|
562
627
|
this.Pattern = init?.Pattern ?? '';
|
|
563
|
-
this.ctx =
|
|
628
|
+
this.ctx =
|
|
629
|
+
init?.ctx ??
|
|
630
|
+
context.Background();
|
|
564
631
|
}
|
|
565
632
|
Context() {
|
|
566
633
|
return this.ctx;
|
|
@@ -571,7 +638,9 @@ export class Request {
|
|
|
571
638
|
Clone(ctx) {
|
|
572
639
|
return new Request({
|
|
573
640
|
Method: this.Method,
|
|
574
|
-
URL: this.URL?.clone != null ? this.URL.clone()
|
|
641
|
+
URL: this.URL?.clone != null ? this.URL.clone()
|
|
642
|
+
: this.URL == null ? null
|
|
643
|
+
: { ...this.URL },
|
|
575
644
|
Proto: this.Proto,
|
|
576
645
|
ProtoMajor: this.ProtoMajor,
|
|
577
646
|
ProtoMinor: this.ProtoMinor,
|
|
@@ -601,7 +670,8 @@ export class Request {
|
|
|
601
670
|
return Header_Get(this.Header, 'Referer');
|
|
602
671
|
}
|
|
603
672
|
ProtoAtLeast(major, minor) {
|
|
604
|
-
return this.ProtoMajor > major ||
|
|
673
|
+
return (this.ProtoMajor > major ||
|
|
674
|
+
(this.ProtoMajor === major && this.ProtoMinor >= minor));
|
|
605
675
|
}
|
|
606
676
|
Cookie(name) {
|
|
607
677
|
for (const cookie of Array.from(this.Cookies() ?? [])) {
|
|
@@ -678,7 +748,8 @@ export class Response {
|
|
|
678
748
|
this.TLS = init?.TLS ?? null;
|
|
679
749
|
if (this.Status === '' && this.StatusCode !== 0) {
|
|
680
750
|
const text = StatusText(this.StatusCode);
|
|
681
|
-
this.Status =
|
|
751
|
+
this.Status =
|
|
752
|
+
text === '' ? String(this.StatusCode) : `${this.StatusCode} ${text}`;
|
|
682
753
|
}
|
|
683
754
|
}
|
|
684
755
|
clone() {
|
|
@@ -726,7 +797,8 @@ export class Response {
|
|
|
726
797
|
}
|
|
727
798
|
}
|
|
728
799
|
ProtoAtLeast(major, minor) {
|
|
729
|
-
return this.ProtoMajor > major ||
|
|
800
|
+
return (this.ProtoMajor > major ||
|
|
801
|
+
(this.ProtoMajor === major && this.ProtoMinor >= minor));
|
|
730
802
|
}
|
|
731
803
|
Write(w) {
|
|
732
804
|
const write = (data) => {
|
|
@@ -820,10 +892,8 @@ function encodeFormData(data) {
|
|
|
820
892
|
if (data instanceof URLSearchParams) {
|
|
821
893
|
return data.toString();
|
|
822
894
|
}
|
|
823
|
-
const entries = data instanceof Map ?
|
|
824
|
-
|
|
825
|
-
: typeof data === 'object' ?
|
|
826
|
-
Object.entries(data)
|
|
895
|
+
const entries = data instanceof Map ? Array.from(data.entries())
|
|
896
|
+
: typeof data === 'object' ? Object.entries(data)
|
|
827
897
|
: [];
|
|
828
898
|
entries.sort(([a], [b]) => String(a).localeCompare(String(b)));
|
|
829
899
|
const params = new URLSearchParams();
|
|
@@ -1015,7 +1085,10 @@ async function fetchRoundTrip(request) {
|
|
|
1015
1085
|
};
|
|
1016
1086
|
if (typeof globalThis.fetch !== 'function') {
|
|
1017
1087
|
closeRequestBody();
|
|
1018
|
-
return [
|
|
1088
|
+
return [
|
|
1089
|
+
null,
|
|
1090
|
+
errors.New('net/http: Client.Do is not implemented in GoScript'),
|
|
1091
|
+
];
|
|
1019
1092
|
}
|
|
1020
1093
|
const ctxErr = request.Context()?.Err?.();
|
|
1021
1094
|
if (ctxErr != null) {
|
|
@@ -1029,7 +1102,9 @@ async function fetchRoundTrip(request) {
|
|
|
1029
1102
|
}
|
|
1030
1103
|
}
|
|
1031
1104
|
let body;
|
|
1032
|
-
if (requestBody != null &&
|
|
1105
|
+
if (requestBody != null &&
|
|
1106
|
+
request.Method !== MethodGet &&
|
|
1107
|
+
request.Method !== MethodHead) {
|
|
1033
1108
|
const [data, err] = await io.ReadAll(requestBody);
|
|
1034
1109
|
const closeErr = closeRequestBody();
|
|
1035
1110
|
if (err != null) {
|
|
@@ -1046,21 +1121,32 @@ async function fetchRoundTrip(request) {
|
|
|
1046
1121
|
return [null, closeErr];
|
|
1047
1122
|
}
|
|
1048
1123
|
}
|
|
1124
|
+
const fetchContext = newFetchContext(request.Context());
|
|
1049
1125
|
try {
|
|
1050
1126
|
const bodyInit = body == null ? undefined : Uint8Array.from(body).buffer;
|
|
1051
|
-
const fetched = await globalThis.fetch(request.URL?.String?.() ?? '', {
|
|
1127
|
+
const [fetched, fetchErr] = await fetchContext.wait(globalThis.fetch(request.URL?.String?.() ?? '', {
|
|
1052
1128
|
method: request.Method || MethodGet,
|
|
1053
1129
|
headers,
|
|
1054
1130
|
body: bodyInit,
|
|
1055
|
-
|
|
1056
|
-
|
|
1131
|
+
signal: fetchContext.signal,
|
|
1132
|
+
}));
|
|
1133
|
+
if (fetchErr != null || fetched == null) {
|
|
1134
|
+
fetchContext.stop();
|
|
1135
|
+
return [null, fetchErr];
|
|
1136
|
+
}
|
|
1057
1137
|
const respHeader = new Header();
|
|
1058
1138
|
fetched.headers.forEach((value, key) => Header_Add(respHeader, key, value));
|
|
1139
|
+
const responseBody = request.Method === MethodHead ?
|
|
1140
|
+
NoBody
|
|
1141
|
+
: new fetchResponseBody(fetched, request.Context(), fetchContext.abort, fetchContext.stop);
|
|
1142
|
+
if (request.Method === MethodHead) {
|
|
1143
|
+
fetchContext.stop();
|
|
1144
|
+
}
|
|
1059
1145
|
return [
|
|
1060
1146
|
new Response({
|
|
1061
1147
|
Status: `${fetched.status} ${fetched.statusText}`,
|
|
1062
1148
|
StatusCode: fetched.status,
|
|
1063
|
-
Body:
|
|
1149
|
+
Body: responseBody,
|
|
1064
1150
|
Header: respHeader,
|
|
1065
1151
|
ContentLength: Number(fetched.headers.get('content-length') ?? -1),
|
|
1066
1152
|
Request: request,
|
|
@@ -1069,17 +1155,84 @@ async function fetchRoundTrip(request) {
|
|
|
1069
1155
|
];
|
|
1070
1156
|
}
|
|
1071
1157
|
catch (err) {
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1158
|
+
fetchContext.stop();
|
|
1159
|
+
return [null, errorFromUnknown(err)];
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
function newFetchContext(requestContext) {
|
|
1163
|
+
let stopped = false;
|
|
1164
|
+
const watchController = typeof AbortController === 'undefined' ? null : new AbortController();
|
|
1165
|
+
const controller = typeof AbortController === 'undefined' ? null : new AbortController();
|
|
1166
|
+
const abort = () => {
|
|
1167
|
+
if (controller != null && !controller.signal.aborted) {
|
|
1168
|
+
controller.abort(requestContext?.Err?.() ?? context.Canceled);
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
const donePromise = requestContext == null ? null : ((async () => {
|
|
1172
|
+
try {
|
|
1173
|
+
await requestContext.Done().selectReceive(0, watchController?.signal);
|
|
1174
|
+
}
|
|
1175
|
+
catch {
|
|
1176
|
+
// Closed channels and receive wakeups both mean the context is done.
|
|
1177
|
+
}
|
|
1178
|
+
const err = requestContext.Err() ?? context.Canceled;
|
|
1179
|
+
if (!stopped) {
|
|
1180
|
+
abort();
|
|
1181
|
+
}
|
|
1182
|
+
return err;
|
|
1183
|
+
})());
|
|
1184
|
+
return {
|
|
1185
|
+
signal: controller?.signal,
|
|
1186
|
+
abort,
|
|
1187
|
+
stop: () => {
|
|
1188
|
+
stopped = true;
|
|
1189
|
+
watchController?.abort();
|
|
1190
|
+
},
|
|
1191
|
+
wait: async (promise) => {
|
|
1192
|
+
const settle = promise.then((value) => ({ value }), (thrown) => ({ thrown }));
|
|
1193
|
+
if (donePromise == null) {
|
|
1194
|
+
const result = await settle;
|
|
1195
|
+
if ('thrown' in result) {
|
|
1196
|
+
return [null, errorFromUnknown(result.thrown)];
|
|
1197
|
+
}
|
|
1198
|
+
return [result.value, null];
|
|
1199
|
+
}
|
|
1200
|
+
const result = await Promise.race([settle, donePromise.then((err) => ({ err }))]);
|
|
1201
|
+
if ('err' in result) {
|
|
1202
|
+
abort();
|
|
1203
|
+
return [null, result.err];
|
|
1204
|
+
}
|
|
1205
|
+
if ('thrown' in result) {
|
|
1206
|
+
return [
|
|
1207
|
+
null,
|
|
1208
|
+
requestContext?.Err?.() ?? errorFromUnknown(result.thrown),
|
|
1209
|
+
];
|
|
1210
|
+
}
|
|
1211
|
+
return [result.value, null];
|
|
1212
|
+
},
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
function errorFromUnknown(err) {
|
|
1216
|
+
const message = typeof err === 'object' && err != null && 'message' in err ?
|
|
1217
|
+
String(err.message)
|
|
1218
|
+
: String(err);
|
|
1219
|
+
return errors.New(message);
|
|
1220
|
+
}
|
|
1221
|
+
async function readFetchBody(fetched, requestContext, abortFetch) {
|
|
1222
|
+
const fetchContext = newFetchContext(requestContext);
|
|
1223
|
+
const [buffer, err] = await fetchContext.wait(fetched.arrayBuffer());
|
|
1224
|
+
fetchContext.stop();
|
|
1225
|
+
if (err != null) {
|
|
1226
|
+
abortFetch();
|
|
1227
|
+
return [new Uint8Array(), err];
|
|
1076
1228
|
}
|
|
1229
|
+
return [new Uint8Array(buffer ?? new ArrayBuffer(0)), null];
|
|
1077
1230
|
}
|
|
1078
1231
|
export function FS(fsys) {
|
|
1079
1232
|
return {
|
|
1080
|
-
Open(name) {
|
|
1233
|
+
async Open(name) {
|
|
1081
1234
|
const cleaned = cleanFileServerPath(name);
|
|
1082
|
-
const [file, err] = fsys?.Open(cleaned) ?? [null, fs.ErrInvalid];
|
|
1235
|
+
const [file, err] = (await fsys?.Open(cleaned)) ?? [null, fs.ErrInvalid];
|
|
1083
1236
|
if (err != null || file == null) {
|
|
1084
1237
|
return [null, err];
|
|
1085
1238
|
}
|
|
@@ -1094,7 +1247,9 @@ function httpFileFromFSFile(file) {
|
|
|
1094
1247
|
Read: (p) => file.Read(p instanceof Uint8Array ? p : Uint8Array.from(p ?? [])),
|
|
1095
1248
|
Close: () => file.Close(),
|
|
1096
1249
|
Stat: () => file.Stat(),
|
|
1097
|
-
Seek: seek == null ?
|
|
1250
|
+
Seek: seek == null ?
|
|
1251
|
+
() => [0, errors.New('net/http: file does not support seek')]
|
|
1252
|
+
: seek.bind(file),
|
|
1098
1253
|
Readdir: readdir == null ? () => [null, io.EOF] : readdir.bind(file),
|
|
1099
1254
|
};
|
|
1100
1255
|
}
|
|
@@ -1109,13 +1264,13 @@ export function FileServer(root) {
|
|
|
1109
1264
|
Error(w, 'method not allowed', StatusMethodNotAllowed);
|
|
1110
1265
|
return;
|
|
1111
1266
|
}
|
|
1112
|
-
const [file, err] = root?.Open(cleanFileServerPath(req.URL?.Path ?? '')) ?? [null, fs.ErrInvalid];
|
|
1267
|
+
const [file, err] = (await root?.Open(cleanFileServerPath(req.URL?.Path ?? ''))) ?? [null, fs.ErrInvalid];
|
|
1113
1268
|
if (err != null || file == null) {
|
|
1114
1269
|
NotFound(w, req);
|
|
1115
1270
|
return;
|
|
1116
1271
|
}
|
|
1117
1272
|
try {
|
|
1118
|
-
const [info, statErr] = file.Stat();
|
|
1273
|
+
const [info, statErr] = await file.Stat();
|
|
1119
1274
|
if (statErr != null) {
|
|
1120
1275
|
Error(w, statErr.Error(), StatusInternalServerError);
|
|
1121
1276
|
return;
|
|
@@ -1124,21 +1279,26 @@ export function FileServer(root) {
|
|
|
1124
1279
|
NotFound(w, req);
|
|
1125
1280
|
return;
|
|
1126
1281
|
}
|
|
1127
|
-
const
|
|
1128
|
-
if (
|
|
1129
|
-
|
|
1130
|
-
|
|
1282
|
+
const header = await w.Header();
|
|
1283
|
+
if (Header_Get(header, 'Content-Type') === '') {
|
|
1284
|
+
const contentType = mime.TypeByExtension(path.Ext(info?.Name?.() || req.URL?.Path || ''));
|
|
1285
|
+
if (contentType !== '') {
|
|
1286
|
+
Header_Set(header, 'Content-Type', contentType);
|
|
1287
|
+
}
|
|
1131
1288
|
}
|
|
1132
1289
|
if (info?.Size != null) {
|
|
1133
|
-
Header_Set(
|
|
1290
|
+
Header_Set(header, 'Content-Length', String(info.Size()));
|
|
1134
1291
|
}
|
|
1135
|
-
w.WriteHeader(StatusOK);
|
|
1292
|
+
await w.WriteHeader(StatusOK);
|
|
1136
1293
|
if (req.Method !== MethodHead) {
|
|
1137
|
-
|
|
1294
|
+
const [, copyErr] = await io.Copy(w, file);
|
|
1295
|
+
if (copyErr != null) {
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1138
1298
|
}
|
|
1139
1299
|
}
|
|
1140
1300
|
finally {
|
|
1141
|
-
file.Close();
|
|
1301
|
+
await file.Close();
|
|
1142
1302
|
}
|
|
1143
1303
|
},
|
|
1144
1304
|
};
|
|
@@ -1174,7 +1334,23 @@ function cleanFileServerPath(name) {
|
|
|
1174
1334
|
}
|
|
1175
1335
|
return parts.length === 0 ? '.' : parts.join('/');
|
|
1176
1336
|
}
|
|
1337
|
+
$.registerInterfaceType('http.Handler', null, [
|
|
1338
|
+
{
|
|
1339
|
+
name: 'ServeHTTP',
|
|
1340
|
+
args: [
|
|
1341
|
+
{ name: 'w', type: 'http.ResponseWriter' },
|
|
1342
|
+
{
|
|
1343
|
+
name: 'r',
|
|
1344
|
+
type: { kind: $.TypeKind.Pointer, elemType: 'http.Request' },
|
|
1345
|
+
},
|
|
1346
|
+
],
|
|
1347
|
+
returns: [],
|
|
1348
|
+
},
|
|
1349
|
+
]);
|
|
1177
1350
|
export function HandlerFunc_ServeHTTP(h, w, r) {
|
|
1351
|
+
if (!h) {
|
|
1352
|
+
throw new globalThis.Error('http: nil HandlerFunc');
|
|
1353
|
+
}
|
|
1178
1354
|
return h(w, r);
|
|
1179
1355
|
}
|
|
1180
1356
|
export class CrossOriginProtection {
|
|
@@ -1334,7 +1510,8 @@ export class Server {
|
|
|
1334
1510
|
this.BaseContext = init?.BaseContext ?? null;
|
|
1335
1511
|
this.ConnContext = init?.ConnContext ?? null;
|
|
1336
1512
|
this.Handler = init?.Handler ?? null;
|
|
1337
|
-
this.DisableGeneralOptionsHandler =
|
|
1513
|
+
this.DisableGeneralOptionsHandler =
|
|
1514
|
+
init?.DisableGeneralOptionsHandler ?? false;
|
|
1338
1515
|
this.TLSConfig = init?.TLSConfig ?? null;
|
|
1339
1516
|
this.ReadTimeout = init?.ReadTimeout ?? 0;
|
|
1340
1517
|
this.ReadTimeoutHandler = init?.ReadTimeoutHandler ?? null;
|
|
@@ -1492,7 +1669,9 @@ export function StripPrefix(prefix, handler) {
|
|
|
1492
1669
|
return {
|
|
1493
1670
|
ServeHTTP(w, r) {
|
|
1494
1671
|
const req = $.pointerValue(r);
|
|
1495
|
-
if (req?.URL != null &&
|
|
1672
|
+
if (req?.URL != null &&
|
|
1673
|
+
typeof req.URL.Path === 'string' &&
|
|
1674
|
+
req.URL.Path.startsWith(prefix)) {
|
|
1496
1675
|
req.URL = { ...req.URL, Path: req.URL.Path.slice(prefix.length) || '/' };
|
|
1497
1676
|
}
|
|
1498
1677
|
return handler?.ServeHTTP(w, req);
|
|
@@ -1522,7 +1701,7 @@ export function NotFoundHandler() {
|
|
|
1522
1701
|
export function RedirectHandler(url, code) {
|
|
1523
1702
|
return {
|
|
1524
1703
|
ServeHTTP(w, r) {
|
|
1525
|
-
Redirect(w, r, url, code);
|
|
1704
|
+
return Redirect(w, r, url, code);
|
|
1526
1705
|
},
|
|
1527
1706
|
};
|
|
1528
1707
|
}
|
|
@@ -1544,17 +1723,23 @@ export function Error(w, error, code) {
|
|
|
1544
1723
|
export function NotFound(w, _r) {
|
|
1545
1724
|
Error(w, '404 page not found', StatusNotFound);
|
|
1546
1725
|
}
|
|
1547
|
-
export function Redirect(w, _r, url, code) {
|
|
1548
|
-
|
|
1726
|
+
export async function Redirect(w, _r, url, code) {
|
|
1727
|
+
if (w == null) {
|
|
1728
|
+
return;
|
|
1729
|
+
}
|
|
1730
|
+
const header = await w.Header();
|
|
1549
1731
|
if (header != null) {
|
|
1550
1732
|
Header_Set(header, 'Location', url);
|
|
1551
1733
|
}
|
|
1552
|
-
w
|
|
1734
|
+
await w.WriteHeader(code);
|
|
1553
1735
|
}
|
|
1554
1736
|
export function ParseTime(text) {
|
|
1555
1737
|
const date = new globalThis.Date(text);
|
|
1556
1738
|
if (isNaN(date.getTime())) {
|
|
1557
|
-
return [
|
|
1739
|
+
return [
|
|
1740
|
+
new time.Time(),
|
|
1741
|
+
$.newError(`parsing time "${text}" as HTTP-date: cannot parse`),
|
|
1742
|
+
];
|
|
1558
1743
|
}
|
|
1559
1744
|
return [time.UnixMilli(date.getTime()), null];
|
|
1560
1745
|
}
|
|
@@ -1599,7 +1784,8 @@ export function DetectContentType(data) {
|
|
|
1599
1784
|
if (startsWithBytes(bytes, new Uint8Array([0xef, 0xbb, 0xbf]))) {
|
|
1600
1785
|
return 'text/plain; charset=utf-8';
|
|
1601
1786
|
}
|
|
1602
|
-
if (startsWithBytes(bytes, new Uint8Array([0x00, 0x00, 0x01, 0x00])) ||
|
|
1787
|
+
if (startsWithBytes(bytes, new Uint8Array([0x00, 0x00, 0x01, 0x00])) ||
|
|
1788
|
+
startsWithBytes(bytes, new Uint8Array([0x00, 0x00, 0x02, 0x00]))) {
|
|
1603
1789
|
return 'image/x-icon';
|
|
1604
1790
|
}
|
|
1605
1791
|
if (startsWithASCII(bytes, 'BM')) {
|
|
@@ -1675,7 +1861,10 @@ export function DetectContentType(data) {
|
|
|
1675
1861
|
return 'application/wasm';
|
|
1676
1862
|
}
|
|
1677
1863
|
for (const byte of afterWS) {
|
|
1678
|
-
if (byte <= 0x08 ||
|
|
1864
|
+
if (byte <= 0x08 ||
|
|
1865
|
+
byte === 0x0b ||
|
|
1866
|
+
(byte >= 0x0e && byte <= 0x1a) ||
|
|
1867
|
+
(byte >= 0x1c && byte <= 0x1f)) {
|
|
1679
1868
|
return 'application/octet-stream';
|
|
1680
1869
|
}
|
|
1681
1870
|
}
|
|
@@ -1684,7 +1873,11 @@ export function DetectContentType(data) {
|
|
|
1684
1873
|
function firstNonWhitespace(data) {
|
|
1685
1874
|
for (let i = 0; i < data.length; i++) {
|
|
1686
1875
|
const byte = data[i];
|
|
1687
|
-
if (byte !== 0x09 &&
|
|
1876
|
+
if (byte !== 0x09 &&
|
|
1877
|
+
byte !== 0x0a &&
|
|
1878
|
+
byte !== 0x0c &&
|
|
1879
|
+
byte !== 0x0d &&
|
|
1880
|
+
byte !== 0x20) {
|
|
1688
1881
|
return i;
|
|
1689
1882
|
}
|
|
1690
1883
|
}
|
|
@@ -1807,7 +2000,12 @@ export function ParseSetCookie(line) {
|
|
|
1807
2000
|
if (!ok) {
|
|
1808
2001
|
return [null, errInvalidCookieValue];
|
|
1809
2002
|
}
|
|
1810
|
-
const cookie = new Cookie({
|
|
2003
|
+
const cookie = new Cookie({
|
|
2004
|
+
Name: name,
|
|
2005
|
+
Value: value,
|
|
2006
|
+
Quoted: quoted,
|
|
2007
|
+
Raw: line,
|
|
2008
|
+
});
|
|
1811
2009
|
const unparsed = [];
|
|
1812
2010
|
for (const raw of parts.slice(1)) {
|
|
1813
2011
|
const part = raw.trim();
|
|
@@ -1863,7 +2061,8 @@ export function ParseSetCookie(line) {
|
|
|
1863
2061
|
break;
|
|
1864
2062
|
}
|
|
1865
2063
|
let secs = Number.parseInt(attrValue, 10);
|
|
1866
|
-
if ((secs !== 0 && attrValue[0] === '0') ||
|
|
2064
|
+
if ((secs !== 0 && attrValue[0] === '0') ||
|
|
2065
|
+
!Number.isSafeInteger(secs)) {
|
|
1867
2066
|
break;
|
|
1868
2067
|
}
|
|
1869
2068
|
if (secs <= 0) {
|
|
@@ -1901,7 +2100,10 @@ export function NewRequestWithContext(ctx, method, url, body) {
|
|
|
1901
2100
|
method = MethodGet;
|
|
1902
2101
|
}
|
|
1903
2102
|
if (!isToken(method)) {
|
|
1904
|
-
return [
|
|
2103
|
+
return [
|
|
2104
|
+
null,
|
|
2105
|
+
errors.New(`net/http: invalid method ${JSON.stringify(method)}`),
|
|
2106
|
+
];
|
|
1905
2107
|
}
|
|
1906
2108
|
if (ctx == null) {
|
|
1907
2109
|
return [null, errors.New('net/http: nil Context')];
|
|
@@ -1911,7 +2113,17 @@ export function NewRequestWithContext(ctx, method, url, body) {
|
|
|
1911
2113
|
return [null, err];
|
|
1912
2114
|
}
|
|
1913
2115
|
const bodyInfo = requestBodyInfo(body, 0);
|
|
1914
|
-
return [
|
|
2116
|
+
return [
|
|
2117
|
+
new Request({
|
|
2118
|
+
Method: method,
|
|
2119
|
+
URL: parsedURL,
|
|
2120
|
+
Body: bodyInfo.Body,
|
|
2121
|
+
ContentLength: bodyInfo.ContentLength,
|
|
2122
|
+
Host: parsedURL.Host,
|
|
2123
|
+
ctx,
|
|
2124
|
+
}),
|
|
2125
|
+
null,
|
|
2126
|
+
];
|
|
1915
2127
|
}
|
|
1916
2128
|
export async function Get(_url) {
|
|
1917
2129
|
const [req, err] = NewRequest(MethodGet, _url, null);
|
|
@@ -1984,9 +2196,14 @@ function requestBodyInfo(body, unknownLength) {
|
|
|
1984
2196
|
if (value === NoBody) {
|
|
1985
2197
|
return { Body: NoBody, ContentLength: 0 };
|
|
1986
2198
|
}
|
|
1987
|
-
if (value instanceof bytes.Buffer ||
|
|
2199
|
+
if (value instanceof bytes.Buffer ||
|
|
2200
|
+
value instanceof bytes.Reader ||
|
|
2201
|
+
value instanceof strings.Reader) {
|
|
1988
2202
|
const length = value.Len();
|
|
1989
|
-
return {
|
|
2203
|
+
return {
|
|
2204
|
+
Body: length === 0 ? NoBody : readCloserForBody(body),
|
|
2205
|
+
ContentLength: length,
|
|
2206
|
+
};
|
|
1990
2207
|
}
|
|
1991
2208
|
return { Body: readCloserForBody(body), ContentLength: unknownLength };
|
|
1992
2209
|
}
|