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.
Files changed (160) hide show
  1. package/compiler/gotest/testdata/browserapi/browserapi_test.go +36 -0
  2. package/compiler/lowering.go +279 -16
  3. package/compiler/override-registry_test.go +175 -0
  4. package/compiler/protobuf-ts-binding.go +154 -6
  5. package/compiler/protobuf-ts-binding_test.go +7 -2
  6. package/compiler/runtime-contract.go +2 -0
  7. package/compiler/runtime-contract_test.go +1 -0
  8. package/compiler/semantic-model.go +16 -0
  9. package/compiler/semantic-model_test.go +38 -0
  10. package/compiler/skeleton_test.go +522 -17
  11. package/compiler/typescript-emitter.go +4 -0
  12. package/dist/gs/builtin/builtin.js +7 -9
  13. package/dist/gs/builtin/builtin.js.map +1 -1
  14. package/dist/gs/builtin/defer.js +2 -2
  15. package/dist/gs/builtin/hostio.js +5 -5
  16. package/dist/gs/builtin/hostio.js.map +1 -1
  17. package/dist/gs/builtin/map.js +2 -1
  18. package/dist/gs/builtin/map.js.map +1 -1
  19. package/dist/gs/builtin/slice.d.ts +3 -0
  20. package/dist/gs/builtin/slice.js +39 -0
  21. package/dist/gs/builtin/slice.js.map +1 -1
  22. package/dist/gs/builtin/type.js +49 -0
  23. package/dist/gs/builtin/type.js.map +1 -1
  24. package/dist/gs/compress/gzip/index.d.ts +41 -0
  25. package/dist/gs/compress/gzip/index.js +235 -0
  26. package/dist/gs/compress/gzip/index.js.map +1 -0
  27. package/dist/gs/compress/zlib/index.js +5 -2
  28. package/dist/gs/compress/zlib/index.js.map +1 -1
  29. package/dist/gs/crypto/ecdh/index.js +27 -8
  30. package/dist/gs/crypto/ecdh/index.js.map +1 -1
  31. package/dist/gs/crypto/ed25519/index.js +3 -3
  32. package/dist/gs/crypto/ed25519/index.js.map +1 -1
  33. package/dist/gs/crypto/rand/index.js +6 -3
  34. package/dist/gs/crypto/rand/index.js.map +1 -1
  35. package/dist/gs/embed/index.js +9 -3
  36. package/dist/gs/embed/index.js.map +1 -1
  37. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -0
  38. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +33 -0
  39. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  40. package/dist/gs/github.com/mr-tron/base58/base58/index.js +4 -1
  41. package/dist/gs/github.com/mr-tron/base58/base58/index.js.map +1 -1
  42. package/dist/gs/golang.org/x/crypto/scrypt/index.d.ts +2 -0
  43. package/dist/gs/golang.org/x/crypto/scrypt/index.js +39 -0
  44. package/dist/gs/golang.org/x/crypto/scrypt/index.js.map +1 -0
  45. package/dist/gs/hash/fnv/index.js +13 -5
  46. package/dist/gs/hash/fnv/index.js.map +1 -1
  47. package/dist/gs/io/fs/glob.d.ts +3 -3
  48. package/dist/gs/io/fs/glob.js +9 -9
  49. package/dist/gs/io/fs/glob.js.map +1 -1
  50. package/dist/gs/io/fs/readdir.d.ts +2 -2
  51. package/dist/gs/io/fs/readdir.js +13 -74
  52. package/dist/gs/io/fs/readdir.js.map +1 -1
  53. package/dist/gs/io/fs/readlink.d.ts +1 -1
  54. package/dist/gs/io/fs/readlink.js +2 -2
  55. package/dist/gs/io/fs/readlink.js.map +1 -1
  56. package/dist/gs/io/fs/stat.d.ts +4 -2
  57. package/dist/gs/io/fs/stat.js +12 -73
  58. package/dist/gs/io/fs/stat.js.map +1 -1
  59. package/dist/gs/io/fs/sub.d.ts +2 -2
  60. package/dist/gs/io/fs/sub.js +11 -11
  61. package/dist/gs/io/fs/sub.js.map +1 -1
  62. package/dist/gs/io/fs/walk.js +2 -2
  63. package/dist/gs/io/fs/walk.js.map +1 -1
  64. package/dist/gs/maps/iter.js.map +1 -1
  65. package/dist/gs/maps/maps.js.map +1 -1
  66. package/dist/gs/mime/index.js +5 -2
  67. package/dist/gs/mime/index.js.map +1 -1
  68. package/dist/gs/net/http/httptest/index.js +6 -3
  69. package/dist/gs/net/http/httptest/index.js.map +1 -1
  70. package/dist/gs/net/http/index.d.ts +34 -18
  71. package/dist/gs/net/http/index.js +280 -63
  72. package/dist/gs/net/http/index.js.map +1 -1
  73. package/dist/gs/net/http/pprof/index.d.ts +5 -5
  74. package/dist/gs/net/http/pprof/index.js +21 -21
  75. package/dist/gs/net/http/pprof/index.js.map +1 -1
  76. package/dist/gs/reflect/iter.js +1 -1
  77. package/dist/gs/reflect/iter.js.map +1 -1
  78. package/dist/gs/reflect/type.d.ts +2 -0
  79. package/dist/gs/reflect/type.js +53 -21
  80. package/dist/gs/reflect/type.js.map +1 -1
  81. package/dist/gs/runtime/pprof/index.js.map +1 -1
  82. package/dist/gs/runtime/runtime.js +2 -2
  83. package/dist/gs/runtime/runtime.js.map +1 -1
  84. package/dist/gs/runtime/trace/index.js.map +1 -1
  85. package/dist/gs/slices/slices.d.ts +1 -1
  86. package/dist/gs/slices/slices.js +37 -4
  87. package/dist/gs/slices/slices.js.map +1 -1
  88. package/gs/builtin/builtin.ts +11 -14
  89. package/gs/builtin/defer.ts +2 -2
  90. package/gs/builtin/hostio.ts +5 -5
  91. package/gs/builtin/map.ts +4 -1
  92. package/gs/builtin/runtime-contract.test.ts +25 -0
  93. package/gs/builtin/slice.test.ts +14 -0
  94. package/gs/builtin/slice.ts +64 -0
  95. package/gs/builtin/type.ts +72 -0
  96. package/gs/bytes/bytes.test.ts +14 -13
  97. package/gs/compress/gzip/index.test.ts +86 -0
  98. package/gs/compress/gzip/index.ts +297 -0
  99. package/gs/compress/gzip/meta.json +6 -0
  100. package/gs/compress/gzip/parity.json +45 -0
  101. package/gs/compress/zlib/index.test.ts +19 -5
  102. package/gs/compress/zlib/index.ts +16 -7
  103. package/gs/context/context.test.ts +3 -1
  104. package/gs/crypto/ecdh/index.test.ts +6 -2
  105. package/gs/crypto/ecdh/index.ts +49 -12
  106. package/gs/crypto/ed25519/index.ts +20 -7
  107. package/gs/crypto/rand/index.ts +6 -3
  108. package/gs/embed/index.test.ts +4 -4
  109. package/gs/embed/index.ts +9 -3
  110. package/gs/fmt/fmt.test.ts +29 -4
  111. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +126 -0
  112. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +46 -0
  113. package/gs/github.com/mr-tron/base58/base58/index.ts +9 -3
  114. package/gs/github.com/zeebo/blake3/internal/consts/index.test.ts +2 -8
  115. package/gs/golang.org/x/crypto/scrypt/index.test.ts +81 -0
  116. package/gs/golang.org/x/crypto/scrypt/index.ts +54 -0
  117. package/gs/golang.org/x/crypto/scrypt/meta.json +5 -0
  118. package/gs/hash/fnv/index.test.ts +1 -8
  119. package/gs/hash/fnv/index.ts +27 -10
  120. package/gs/io/fs/glob.ts +14 -11
  121. package/gs/io/fs/meta.json +5 -0
  122. package/gs/io/fs/readdir.test.ts +63 -2
  123. package/gs/io/fs/readdir.ts +33 -30
  124. package/gs/io/fs/readlink.test.ts +2 -2
  125. package/gs/io/fs/readlink.ts +5 -2
  126. package/gs/io/fs/stat.test.ts +79 -0
  127. package/gs/io/fs/stat.ts +24 -10
  128. package/gs/io/fs/sub.test.ts +93 -0
  129. package/gs/io/fs/sub.ts +13 -13
  130. package/gs/io/fs/walk.ts +2 -2
  131. package/gs/maps/iter.ts +9 -9
  132. package/gs/maps/maps.ts +4 -4
  133. package/gs/math/bits/index.test.ts +10 -1
  134. package/gs/mime/index.test.ts +33 -15
  135. package/gs/mime/index.ts +9 -2
  136. package/gs/net/http/httptest/index.test.ts +17 -3
  137. package/gs/net/http/httptest/index.ts +8 -3
  138. package/gs/net/http/index.test.ts +851 -124
  139. package/gs/net/http/index.ts +612 -146
  140. package/gs/net/http/meta.json +3 -1
  141. package/gs/net/http/pprof/index.test.ts +4 -4
  142. package/gs/net/http/pprof/index.ts +43 -22
  143. package/gs/os/file_unix_js.test.ts +22 -0
  144. package/gs/reflect/iter.ts +4 -2
  145. package/gs/reflect/map.test.ts +56 -1
  146. package/gs/reflect/type.ts +76 -37
  147. package/gs/runtime/pprof/index.test.ts +7 -1
  148. package/gs/runtime/pprof/index.ts +5 -1
  149. package/gs/runtime/runtime.test.ts +7 -0
  150. package/gs/runtime/runtime.ts +2 -4
  151. package/gs/runtime/trace/index.test.ts +9 -1
  152. package/gs/runtime/trace/index.ts +5 -1
  153. package/gs/slices/meta.json +3 -0
  154. package/gs/slices/slices.test.ts +59 -21
  155. package/gs/slices/slices.ts +61 -20
  156. package/gs/strconv/complex.test.ts +17 -3
  157. package/gs/sync/atomic/doc_64.test.ts +2 -9
  158. package/gs/sync/sync.test.ts +18 -8
  159. package/gs/syscall/js/index.test.ts +9 -4
  160. 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
 
@@ -110,33 +112,55 @@ export class MaxBytesError {
110
112
  }
111
113
 
112
114
  export const ErrNotSupported = new ProtocolError('feature not supported')
113
- export const ErrUnexpectedTrailer = new ProtocolError('trailer header without chunked transfer encoding')
114
- export const ErrMissingBoundary = new ProtocolError('no multipart boundary param in Content-Type')
115
- export const ErrNotMultipart = new ProtocolError("request Content-Type isn't multipart/form-data")
115
+ export const ErrUnexpectedTrailer = new ProtocolError(
116
+ 'trailer header without chunked transfer encoding',
117
+ )
118
+ export const ErrMissingBoundary = new ProtocolError(
119
+ 'no multipart boundary param in Content-Type',
120
+ )
121
+ export const ErrNotMultipart = new ProtocolError(
122
+ "request Content-Type isn't multipart/form-data",
123
+ )
116
124
  export const ErrHeaderTooLong = new ProtocolError('header too long')
117
125
  export const ErrShortBody = new ProtocolError('entity body too short')
118
- export const ErrMissingContentLength = new ProtocolError('missing ContentLength in HEAD response')
119
- export const ErrBodyNotAllowed = errors.New('http: request method or response status code does not allow body')
120
- export const ErrBodyReadAfterClose = errors.New('http: invalid Read on closed Body')
121
- export const ErrContentLength = errors.New('http: wrote more than the declared Content-Length')
126
+ export const ErrMissingContentLength = new ProtocolError(
127
+ 'missing ContentLength in HEAD response',
128
+ )
129
+ export const ErrBodyNotAllowed = errors.New(
130
+ 'http: request method or response status code does not allow body',
131
+ )
132
+ export const ErrBodyReadAfterClose = errors.New(
133
+ 'http: invalid Read on closed Body',
134
+ )
135
+ export const ErrContentLength = errors.New(
136
+ 'http: wrote more than the declared Content-Length',
137
+ )
122
138
  export const ErrHandlerTimeout = errors.New('http: Handler timeout')
123
139
  export const ErrHijacked = errors.New('http: connection has been hijacked')
124
140
  export const ErrLineTooLong = errors.New('header line too long')
125
141
  export const ErrMissingFile = errors.New('http: no such file')
126
142
  export const ErrNoCookie = errors.New('http: named cookie not present')
127
143
  export const ErrNoLocation = errors.New('http: no Location header in response')
128
- export const ErrSchemeMismatch = errors.New('http: server gave HTTP response to HTTPS client')
144
+ export const ErrSchemeMismatch = errors.New(
145
+ 'http: server gave HTTP response to HTTPS client',
146
+ )
129
147
  export const ErrServerClosed = errors.New('http: Server closed')
130
148
  export const ErrAbortHandler = errors.New('net/http: abort Handler')
131
- export const ErrSkipAltProtocol = errors.New('net/http: skip alternate protocol')
149
+ export const ErrSkipAltProtocol = errors.New(
150
+ 'net/http: skip alternate protocol',
151
+ )
132
152
  export const ErrUseLastResponse = errors.New('net/http: use last response')
133
153
  export const ErrWriteAfterFlush = errors.New('unused')
134
154
  const errBlankCookie = errors.New('http: blank cookie')
135
155
  const errEqualNotFoundInCookie = errors.New("http: '=' not found in cookie")
136
156
  const errInvalidCookieName = errors.New('http: invalid cookie name')
137
157
  const errInvalidCookieValue = errors.New('http: invalid cookie value')
138
- const errCookieNumLimitExceeded = errors.New('http: number of cookies exceeded limit')
139
- const errCrossOriginRequest = errors.New('cross-origin request detected from Sec-Fetch-Site header')
158
+ const errCookieNumLimitExceeded = errors.New(
159
+ 'http: number of cookies exceeded limit',
160
+ )
161
+ const errCrossOriginRequest = errors.New(
162
+ 'cross-origin request detected from Sec-Fetch-Site header',
163
+ )
140
164
  const errCrossOriginRequestFromOldBrowser = errors.New(
141
165
  'cross-origin request detected, and/or browser is out of date: Sec-Fetch-Site is missing, and Origin does not match Host',
142
166
  )
@@ -226,53 +250,73 @@ export function StatusText(code: number): string {
226
250
  }
227
251
 
228
252
  export type Header = Map<string, $.Slice<string>>
253
+ type HeaderBox = { __goValue: HeaderValue }
254
+ type HeaderValue = Header | $.VarRef<Header> | HeaderBox
229
255
 
230
256
  export const Header = Map as {
231
- new(entries?: Iterable<readonly [string, $.Slice<string>]> | null): Header
257
+ new (entries?: Iterable<readonly [string, $.Slice<string>]> | null): Header
232
258
  }
233
259
 
234
260
  export function CanonicalHeaderKey(s: string): string {
235
261
  return canonicalMIMEHeaderKey(s)
236
262
  }
237
263
 
238
- export function Header_Add(h: Header, key: string, value: string): void {
264
+ function headerMap(h: HeaderValue): Header {
265
+ let value: unknown = $.pointerValue(h as Header | $.VarRef<Header>)
266
+ while (
267
+ value !== null &&
268
+ value !== undefined &&
269
+ typeof value === 'object' &&
270
+ '__goValue' in value
271
+ ) {
272
+ value = $.pointerValue((value as HeaderBox).__goValue)
273
+ }
274
+ return value as Header
275
+ }
276
+
277
+ export function Header_Add(h: HeaderValue, key: string, value: string): void {
278
+ const headers = headerMap(h)
239
279
  key = canonicalMIMEHeaderKey(key)
240
- const values = Array.from(h.get(key) ?? [])
280
+ const values = Array.from(headers.get(key) ?? [])
241
281
  values.push(value)
242
- h.set(key, $.arrayToSlice(values))
282
+ headers.set(key, $.arrayToSlice(values))
243
283
  }
244
284
 
245
- export function Header_Del(h: Header, key: string): void {
246
- h.delete(canonicalMIMEHeaderKey(key))
285
+ export function Header_Del(h: HeaderValue, key: string): void {
286
+ headerMap(h).delete(canonicalMIMEHeaderKey(key))
247
287
  }
248
288
 
249
- export function Header_Get(h: Header, key: string): string {
250
- const values = h.get(canonicalMIMEHeaderKey(key))
289
+ export function Header_Get(h: HeaderValue, key: string): string {
290
+ const values = headerMap(h).get(canonicalMIMEHeaderKey(key))
251
291
  return values == null || values.length === 0 ? '' : String(values[0])
252
292
  }
253
293
 
254
- export function Header_Set(h: Header, key: string, value: string): void {
255
- h.set(canonicalMIMEHeaderKey(key), $.arrayToSlice([value]))
294
+ export function Header_Set(h: HeaderValue, key: string, value: string): void {
295
+ headerMap(h).set(canonicalMIMEHeaderKey(key), $.arrayToSlice([value]))
256
296
  }
257
297
 
258
- export function Header_Values(h: Header, key: string): $.Slice<string> {
259
- return h.get(canonicalMIMEHeaderKey(key)) ?? null
298
+ export function Header_Values(h: HeaderValue, key: string): $.Slice<string> {
299
+ return headerMap(h).get(canonicalMIMEHeaderKey(key)) ?? null
260
300
  }
261
301
 
262
- export function Header_Clone(h: Header): Header {
302
+ export function Header_Clone(h: HeaderValue): Header {
263
303
  const cloned = new Header()
264
- for (const [key, values] of h.entries()) {
304
+ for (const [key, values] of headerMap(h).entries()) {
265
305
  cloned.set(key, $.arrayToSlice(Array.from(values ?? [])))
266
306
  }
267
307
  return cloned
268
308
  }
269
309
 
270
- export function Header_Write(h: Header, w: io.Writer): $.GoError {
310
+ export function Header_Write(h: HeaderValue, w: io.Writer): $.GoError {
271
311
  return Header_WriteSubset(h, w, null)
272
312
  }
273
313
 
274
- export function Header_WriteSubset(h: Header, w: io.Writer, exclude: Map<string, boolean> | null): $.GoError {
275
- for (const [key, values] of h.entries()) {
314
+ export function Header_WriteSubset(
315
+ h: HeaderValue,
316
+ w: io.Writer,
317
+ exclude: Map<string, boolean> | null,
318
+ ): $.GoError {
319
+ for (const [key, values] of headerMap(h).entries()) {
276
320
  if (exclude?.get(key) === true) {
277
321
  continue
278
322
  }
@@ -377,12 +421,15 @@ function parseRequestURL(rawURL: string): [RequestURL | null, $.GoError] {
377
421
  }
378
422
  const parsed = new URL(rawURL, 'http://goscript.invalid')
379
423
  const hasHost = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(rawURL)
380
- return [new RequestURL(
381
- parsed.pathname,
382
- parsed.search.startsWith('?') ? parsed.search.slice(1) : parsed.search,
383
- hasHost ? parsed.protocol.replace(/:$/, '') : '',
384
- hasHost ? parsed.host : '',
385
- ), null]
424
+ return [
425
+ new RequestURL(
426
+ parsed.pathname,
427
+ parsed.search.startsWith('?') ? parsed.search.slice(1) : parsed.search,
428
+ hasHost ? parsed.protocol.replace(/:$/, '') : '',
429
+ hasHost ? parsed.host : '',
430
+ ),
431
+ null,
432
+ ]
386
433
  } catch {
387
434
  return [null, errors.New(`parse "${rawURL}": invalid URL`)]
388
435
  }
@@ -404,6 +451,50 @@ class responseBody implements io.ReadCloser {
404
451
  }
405
452
  }
406
453
 
454
+ class fetchResponseBody {
455
+ private reader: bytes.Reader | null = null
456
+ private closed = false
457
+
458
+ constructor(
459
+ private fetched: globalThis.Response,
460
+ private requestContext: context.Context,
461
+ private abortFetch: () => void,
462
+ private stopContextWatch: () => void,
463
+ ) {}
464
+
465
+ public async Read(p: $.Bytes): Promise<[number, $.GoError]> {
466
+ if (this.closed) {
467
+ return [0, ErrBodyReadAfterClose]
468
+ }
469
+ if (this.reader == null) {
470
+ const [data, err] = await readFetchBody(
471
+ this.fetched,
472
+ this.requestContext,
473
+ this.abortFetch,
474
+ )
475
+ if (err != null) {
476
+ this.stopContextWatch()
477
+ return [0, err]
478
+ }
479
+ this.stopContextWatch()
480
+ this.reader = bytes.NewReader(data)
481
+ }
482
+ return this.reader.Read(p)
483
+ }
484
+
485
+ public Close(): $.GoError {
486
+ if (this.closed) {
487
+ return null
488
+ }
489
+ this.closed = true
490
+ this.stopContextWatch()
491
+ if (this.reader == null) {
492
+ this.abortFetch()
493
+ }
494
+ return null
495
+ }
496
+ }
497
+
407
498
  class noBody implements io.ReadCloser {
408
499
  public Read(_p: $.Bytes): [number, $.GoError] {
409
500
  return [0, io.EOF]
@@ -454,7 +545,9 @@ export class Cookie {
454
545
  }
455
546
 
456
547
  public String(): string {
457
- const parts = [`${this.Name}=${this.Quoted ? quoteCookieValue(this.Value) : this.Value}`]
548
+ const parts = [
549
+ `${this.Name}=${this.Quoted ? quoteCookieValue(this.Value) : this.Value}`,
550
+ ]
458
551
  if (this.Path !== '') {
459
552
  parts.push(`Path=${this.Path}`)
460
553
  }
@@ -499,13 +592,27 @@ function isToken(value: string): boolean {
499
592
  }
500
593
 
501
594
  function validCookieValueByte(code: number): boolean {
502
- return code >= 0x20 && code < 0x7f && code !== 0x22 && code !== 0x3b && code !== 0x5c
503
- }
504
-
505
- function parseCookieValue(raw: string, allowDoubleQuote: boolean): [string, boolean, boolean] {
595
+ return (
596
+ code >= 0x20 &&
597
+ code < 0x7f &&
598
+ code !== 0x22 &&
599
+ code !== 0x3b &&
600
+ code !== 0x5c
601
+ )
602
+ }
603
+
604
+ function parseCookieValue(
605
+ raw: string,
606
+ allowDoubleQuote: boolean,
607
+ ): [string, boolean, boolean] {
506
608
  let value = raw
507
609
  let quoted = false
508
- if (allowDoubleQuote && value.length > 1 && value[0] === '"' && value[value.length - 1] === '"') {
610
+ if (
611
+ allowDoubleQuote &&
612
+ value.length > 1 &&
613
+ value[0] === '"' &&
614
+ value[value.length - 1] === '"'
615
+ ) {
509
616
  value = value.slice(1, -1)
510
617
  quoted = true
511
618
  }
@@ -526,12 +633,15 @@ function asciiLower(value: string): [string, boolean] {
526
633
  return [value.toLowerCase(), true]
527
634
  }
528
635
 
529
- export function SetCookie(w: ResponseWriter | null, cookie: Cookie | $.VarRef<Cookie> | null): void {
636
+ export async function SetCookie(
637
+ w: ResponseWriter | null,
638
+ cookie: Cookie | $.VarRef<Cookie> | null,
639
+ ): Promise<void> {
530
640
  const c = $.pointerValue<Cookie | null>(cookie)
531
641
  if (w == null || c == null) {
532
642
  return
533
643
  }
534
- Header_Add(w.Header(), 'Set-Cookie', c.String())
644
+ Header_Add(await w.Header(), 'Set-Cookie', c.String())
535
645
  }
536
646
 
537
647
  class memoryResponseWriter implements ResponseWriter {
@@ -592,7 +702,7 @@ function inProcessServerRequest(request: Request): Request {
592
702
  const rawQuery = request.URL?.RawQuery ?? ''
593
703
  const query = rawQuery === '' ? '' : `?${rawQuery}`
594
704
  req.RequestURI = `${request.URL?.Path ?? '/'}${query}`
595
- req.Host = request.Host === '' ? request.URL?.Host ?? '' : request.Host
705
+ req.Host = request.Host === '' ? (request.URL?.Host ?? '') : request.Host
596
706
  if (req.URL?.clone != null) {
597
707
  req.URL = req.URL.clone()
598
708
  req.URL.Scheme = ''
@@ -602,9 +712,9 @@ function inProcessServerRequest(request: Request): Request {
602
712
  }
603
713
 
604
714
  export interface ResponseWriter {
605
- Header(): Header
606
- Write(p: $.Slice<number>): [number, $.GoError]
607
- WriteHeader(statusCode: number): void
715
+ Header(): Header | Promise<Header>
716
+ Write(p: $.Slice<number>): [number, $.GoError] | Promise<[number, $.GoError]>
717
+ WriteHeader(statusCode: number): void | Promise<void>
608
718
  }
609
719
 
610
720
  export class Request {
@@ -653,7 +763,9 @@ export class Request {
653
763
  this.Cancel = init?.Cancel ?? null
654
764
  this.Response = init?.Response ?? null
655
765
  this.Pattern = init?.Pattern ?? ''
656
- this.ctx = (init as { ctx?: context.Context } | undefined)?.ctx ?? context.Background()
766
+ this.ctx =
767
+ (init as { ctx?: context.Context } | undefined)?.ctx ??
768
+ context.Background()
657
769
  }
658
770
 
659
771
  public Context(): context.Context {
@@ -667,7 +779,10 @@ export class Request {
667
779
  public Clone(ctx: context.Context): Request {
668
780
  return new Request({
669
781
  Method: this.Method,
670
- URL: this.URL?.clone != null ? this.URL.clone() : this.URL == null ? null : { ...this.URL },
782
+ URL:
783
+ this.URL?.clone != null ? this.URL.clone()
784
+ : this.URL == null ? null
785
+ : { ...this.URL },
671
786
  Proto: this.Proto,
672
787
  ProtoMajor: this.ProtoMajor,
673
788
  ProtoMinor: this.ProtoMinor,
@@ -700,7 +815,10 @@ export class Request {
700
815
  }
701
816
 
702
817
  public ProtoAtLeast(major: number, minor: number): boolean {
703
- return this.ProtoMajor > major || (this.ProtoMajor === major && this.ProtoMinor >= minor)
818
+ return (
819
+ this.ProtoMajor > major ||
820
+ (this.ProtoMajor === major && this.ProtoMinor >= minor)
821
+ )
704
822
  }
705
823
 
706
824
  public Cookie(name: string): [Cookie | null, $.GoError] {
@@ -785,7 +903,8 @@ export class Response {
785
903
  this.TLS = init?.TLS ?? null
786
904
  if (this.Status === '' && this.StatusCode !== 0) {
787
905
  const text = StatusText(this.StatusCode)
788
- this.Status = text === '' ? String(this.StatusCode) : `${this.StatusCode} ${text}`
906
+ this.Status =
907
+ text === '' ? String(this.StatusCode) : `${this.StatusCode} ${text}`
789
908
  }
790
909
  }
791
910
 
@@ -836,7 +955,10 @@ export class Response {
836
955
  }
837
956
 
838
957
  public ProtoAtLeast(major: number, minor: number): boolean {
839
- return this.ProtoMajor > major || (this.ProtoMajor === major && this.ProtoMinor >= minor)
958
+ return (
959
+ this.ProtoMajor > major ||
960
+ (this.ProtoMajor === major && this.ProtoMinor >= minor)
961
+ )
840
962
  }
841
963
 
842
964
  public Write(w: io.Writer): $.GoError {
@@ -910,7 +1032,11 @@ export class Client {
910
1032
  return await this.Do(req)
911
1033
  }
912
1034
 
913
- public async Post(url: string, contentType: string, body: io.Reader | null): Promise<[Response | null, $.GoError]> {
1035
+ public async Post(
1036
+ url: string,
1037
+ contentType: string,
1038
+ body: io.Reader | null,
1039
+ ): Promise<[Response | null, $.GoError]> {
914
1040
  const [req, err] = NewRequest(MethodPost, url, body)
915
1041
  if (err != null || req == null) {
916
1042
  return [null, err]
@@ -919,12 +1045,21 @@ export class Client {
919
1045
  return await this.Do(req)
920
1046
  }
921
1047
 
922
- public async PostForm(url: string, data: any): Promise<[Response | null, $.GoError]> {
923
- return await this.Post(url, 'application/x-www-form-urlencoded', bytes.NewReader($.stringToBytes(encodeFormData(data))))
1048
+ public async PostForm(
1049
+ url: string,
1050
+ data: any,
1051
+ ): Promise<[Response | null, $.GoError]> {
1052
+ return await this.Post(
1053
+ url,
1054
+ 'application/x-www-form-urlencoded',
1055
+ bytes.NewReader($.stringToBytes(encodeFormData(data))),
1056
+ )
924
1057
  }
925
1058
 
926
1059
  public CloseIdleConnections(): void {
927
- const closer = this.Transport as { CloseIdleConnections?: () => void } | null
1060
+ const closer = this.Transport as {
1061
+ CloseIdleConnections?: () => void
1062
+ } | null
928
1063
  closer?.CloseIdleConnections?.()
929
1064
  }
930
1065
  }
@@ -944,10 +1079,8 @@ function encodeFormData(data: any): string {
944
1079
  return data.toString()
945
1080
  }
946
1081
  const entries =
947
- data instanceof Map ?
948
- Array.from(data.entries())
949
- : typeof data === 'object' ?
950
- Object.entries(data)
1082
+ data instanceof Map ? Array.from(data.entries())
1083
+ : typeof data === 'object' ? Object.entries(data)
951
1084
  : []
952
1085
  entries.sort(([a], [b]) => String(a).localeCompare(String(b)))
953
1086
  const params = new URLSearchParams()
@@ -957,7 +1090,11 @@ function encodeFormData(data: any): string {
957
1090
  return params.toString()
958
1091
  }
959
1092
 
960
- function appendFormValue(params: URLSearchParams, key: string, value: unknown): void {
1093
+ function appendFormValue(
1094
+ params: URLSearchParams,
1095
+ key: string,
1096
+ value: unknown,
1097
+ ): void {
961
1098
  const unwrapped = unwrapFormValue(value)
962
1099
  if (unwrapped == null) {
963
1100
  return
@@ -982,7 +1119,9 @@ function unwrapFormValue(value: unknown): unknown {
982
1119
  }
983
1120
 
984
1121
  export interface RoundTripper {
985
- RoundTrip(req: Request | $.VarRef<Request> | null): [Response | null, $.GoError] | Promise<[Response | null, $.GoError]>
1122
+ RoundTrip(
1123
+ req: Request | $.VarRef<Request> | null,
1124
+ ): [Response | null, $.GoError] | Promise<[Response | null, $.GoError]>
986
1125
  }
987
1126
 
988
1127
  export class Protocols {
@@ -1051,8 +1190,17 @@ export class HTTP2Config {
1051
1190
  }
1052
1191
 
1053
1192
  export class Transport implements RoundTripper {
1054
- public Proxy: ((req: Request | $.VarRef<Request> | null) => [any, $.GoError]) | null = null
1055
- public OnProxyConnectResponse: ((ctx: context.Context, proxyURL: any, connectReq: Request, connectRes: Response) => $.GoError) | null = null
1193
+ public Proxy:
1194
+ | ((req: Request | $.VarRef<Request> | null) => [any, $.GoError])
1195
+ | null = null
1196
+ public OnProxyConnectResponse:
1197
+ | ((
1198
+ ctx: context.Context,
1199
+ proxyURL: any,
1200
+ connectReq: Request,
1201
+ connectRes: Response,
1202
+ ) => $.GoError)
1203
+ | null = null
1056
1204
  public DialContext: any = null
1057
1205
  public Dial: any = null
1058
1206
  public DialTLSContext: any = null
@@ -1069,7 +1217,13 @@ export class Transport implements RoundTripper {
1069
1217
  public ExpectContinueTimeout = 0
1070
1218
  public TLSNextProto: Map<string, any> | null = null
1071
1219
  public ProxyConnectHeader = new Header()
1072
- public GetProxyConnectHeader: ((ctx: context.Context, proxyURL: any, target: string) => [Header | null, $.GoError]) | null = null
1220
+ public GetProxyConnectHeader:
1221
+ | ((
1222
+ ctx: context.Context,
1223
+ proxyURL: any,
1224
+ target: string,
1225
+ ) => [Header | null, $.GoError])
1226
+ | null = null
1073
1227
  public MaxResponseHeaderBytes = 0
1074
1228
  public WriteBufferSize = 0
1075
1229
  public ReadBufferSize = 0
@@ -1081,7 +1235,9 @@ export class Transport implements RoundTripper {
1081
1235
  Object.assign(this, init)
1082
1236
  }
1083
1237
 
1084
- public async RoundTrip(req: Request | $.VarRef<Request> | null): Promise<[Response | null, $.GoError]> {
1238
+ public async RoundTrip(
1239
+ req: Request | $.VarRef<Request> | null,
1240
+ ): Promise<[Response | null, $.GoError]> {
1085
1241
  const request = $.pointerValue<Request | null>(req)
1086
1242
  if (request == null) {
1087
1243
  return [null, errors.New('net/http: nil Request')]
@@ -1094,7 +1250,10 @@ export class Transport implements RoundTripper {
1094
1250
  const recorder = new memoryResponseWriter()
1095
1251
  let closeErr: $.GoError | undefined
1096
1252
  try {
1097
- const served = handler.ServeHTTP(recorder, inProcessServerRequest(request))
1253
+ const served = handler.ServeHTTP(
1254
+ recorder,
1255
+ inProcessServerRequest(request),
1256
+ )
1098
1257
  if (served instanceof Promise) {
1099
1258
  await served
1100
1259
  }
@@ -1117,7 +1276,11 @@ export class Transport implements RoundTripper {
1117
1276
 
1118
1277
  public RegisterProtocol(_scheme: string, _rt: RoundTripper): void {}
1119
1278
 
1120
- public NewClientConn(_ctx: context.Context, _scheme: string, _address: string): [ClientConn | null, $.GoError] {
1279
+ public NewClientConn(
1280
+ _ctx: context.Context,
1281
+ _scheme: string,
1282
+ _address: string,
1283
+ ): [ClientConn | null, $.GoError] {
1121
1284
  return [null, ErrNotSupported]
1122
1285
  }
1123
1286
 
@@ -1131,7 +1294,9 @@ export const DefaultTransport: RoundTripper = new Transport()
1131
1294
  class fileTransport implements RoundTripper {
1132
1295
  constructor(private root: FileSystem | null) {}
1133
1296
 
1134
- public async RoundTrip(req: Request | $.VarRef<Request> | null): Promise<[Response | null, $.GoError]> {
1297
+ public async RoundTrip(
1298
+ req: Request | $.VarRef<Request> | null,
1299
+ ): Promise<[Response | null, $.GoError]> {
1135
1300
  const request = $.pointerValue<Request | null>(req)
1136
1301
  const recorder = new memoryResponseWriter()
1137
1302
  let closeErr: $.GoError | undefined
@@ -1155,7 +1320,9 @@ export function NewFileTransportFS(fsys: fs.FS): RoundTripper {
1155
1320
  return NewFileTransport(FS(fsys))
1156
1321
  }
1157
1322
 
1158
- async function fetchRoundTrip(request: Request): Promise<[Response | null, $.GoError]> {
1323
+ async function fetchRoundTrip(
1324
+ request: Request,
1325
+ ): Promise<[Response | null, $.GoError]> {
1159
1326
  const requestBody = request.Body
1160
1327
  const closeRequestBody = (): $.GoError => {
1161
1328
  if (requestBody == null) {
@@ -1165,7 +1332,10 @@ async function fetchRoundTrip(request: Request): Promise<[Response | null, $.GoE
1165
1332
  }
1166
1333
  if (typeof globalThis.fetch !== 'function') {
1167
1334
  closeRequestBody()
1168
- return [null, errors.New('net/http: Client.Do is not implemented in GoScript')]
1335
+ return [
1336
+ null,
1337
+ errors.New('net/http: Client.Do is not implemented in GoScript'),
1338
+ ]
1169
1339
  }
1170
1340
  const ctxErr = request.Context()?.Err?.()
1171
1341
  if (ctxErr != null) {
@@ -1179,7 +1349,11 @@ async function fetchRoundTrip(request: Request): Promise<[Response | null, $.GoE
1179
1349
  }
1180
1350
  }
1181
1351
  let body: Uint8Array | undefined
1182
- if (requestBody != null && request.Method !== MethodGet && request.Method !== MethodHead) {
1352
+ if (
1353
+ requestBody != null &&
1354
+ request.Method !== MethodGet &&
1355
+ request.Method !== MethodHead
1356
+ ) {
1183
1357
  const [data, err] = await io.ReadAll(requestBody)
1184
1358
  const closeErr = closeRequestBody()
1185
1359
  if (err != null) {
@@ -1195,21 +1369,40 @@ async function fetchRoundTrip(request: Request): Promise<[Response | null, $.GoE
1195
1369
  return [null, closeErr]
1196
1370
  }
1197
1371
  }
1372
+ const fetchContext = newFetchContext(request.Context())
1198
1373
  try {
1199
1374
  const bodyInit = body == null ? undefined : Uint8Array.from(body).buffer
1200
- const fetched = await globalThis.fetch(request.URL?.String?.() ?? '', {
1201
- method: request.Method || MethodGet,
1202
- headers,
1203
- body: bodyInit,
1204
- })
1205
- const data = new Uint8Array(await fetched.arrayBuffer())
1375
+ const [fetched, fetchErr] = await fetchContext.wait(
1376
+ globalThis.fetch(request.URL?.String?.() ?? '', {
1377
+ method: request.Method || MethodGet,
1378
+ headers,
1379
+ body: bodyInit,
1380
+ signal: fetchContext.signal,
1381
+ }),
1382
+ )
1383
+ if (fetchErr != null || fetched == null) {
1384
+ fetchContext.stop()
1385
+ return [null, fetchErr]
1386
+ }
1206
1387
  const respHeader = new Header()
1207
1388
  fetched.headers.forEach((value, key) => Header_Add(respHeader, key, value))
1389
+ const responseBody: io.ReadCloser =
1390
+ request.Method === MethodHead ?
1391
+ NoBody
1392
+ : (new fetchResponseBody(
1393
+ fetched,
1394
+ request.Context(),
1395
+ fetchContext.abort,
1396
+ fetchContext.stop,
1397
+ ) as unknown as io.ReadCloser)
1398
+ if (request.Method === MethodHead) {
1399
+ fetchContext.stop()
1400
+ }
1208
1401
  return [
1209
1402
  new Response({
1210
1403
  Status: `${fetched.status} ${fetched.statusText}`,
1211
1404
  StatusCode: fetched.status,
1212
- Body: new responseBody(data),
1405
+ Body: responseBody,
1213
1406
  Header: respHeader,
1214
1407
  ContentLength: Number(fetched.headers.get('content-length') ?? -1),
1215
1408
  Request: request,
@@ -1217,27 +1410,130 @@ async function fetchRoundTrip(request: Request): Promise<[Response | null, $.GoE
1217
1410
  null,
1218
1411
  ]
1219
1412
  } catch (err) {
1220
- const message = typeof err === 'object' && err != null && 'message' in err
1221
- ? String((err as { message: unknown }).message)
1222
- : String(err)
1223
- return [null, errors.New(message)]
1413
+ fetchContext.stop()
1414
+ return [null, errorFromUnknown(err)]
1415
+ }
1416
+ }
1417
+
1418
+ function newFetchContext(requestContext: context.Context): {
1419
+ signal: AbortSignal | undefined
1420
+ abort: () => void
1421
+ stop: () => void
1422
+ wait: <T>(promise: Promise<T>) => Promise<[T | null, $.GoError]>
1423
+ } {
1424
+ let stopped = false
1425
+ const watchController =
1426
+ typeof AbortController === 'undefined' ? null : new AbortController()
1427
+ const controller =
1428
+ typeof AbortController === 'undefined' ? null : new AbortController()
1429
+ const abort = () => {
1430
+ if (controller != null && !controller.signal.aborted) {
1431
+ controller.abort(requestContext?.Err?.() ?? context.Canceled)
1432
+ }
1433
+ }
1434
+ const donePromise =
1435
+ requestContext == null ? null : (
1436
+ (async (): Promise<$.GoError> => {
1437
+ try {
1438
+ await requestContext.Done().selectReceive(0, watchController?.signal)
1439
+ } catch {
1440
+ // Closed channels and receive wakeups both mean the context is done.
1441
+ }
1442
+ const err = requestContext.Err() ?? context.Canceled
1443
+ if (!stopped) {
1444
+ abort()
1445
+ }
1446
+ return err
1447
+ })()
1448
+ )
1449
+ return {
1450
+ signal: controller?.signal,
1451
+ abort,
1452
+ stop: () => {
1453
+ stopped = true
1454
+ watchController?.abort()
1455
+ },
1456
+ wait: async <T>(promise: Promise<T>): Promise<[T | null, $.GoError]> => {
1457
+ const settle = promise.then(
1458
+ (value) => ({ value }),
1459
+ (thrown) => ({ thrown }),
1460
+ )
1461
+ if (donePromise == null) {
1462
+ const result = await settle
1463
+ if ('thrown' in result) {
1464
+ return [null, errorFromUnknown(result.thrown)]
1465
+ }
1466
+ return [result.value, null]
1467
+ }
1468
+ const result = await Promise.race<
1469
+ { value: T } | { err: $.GoError } | { thrown: unknown }
1470
+ >([settle, donePromise.then((err) => ({ err }))])
1471
+ if ('err' in result) {
1472
+ abort()
1473
+ return [null, result.err]
1474
+ }
1475
+ if ('thrown' in result) {
1476
+ return [
1477
+ null,
1478
+ requestContext?.Err?.() ?? errorFromUnknown(result.thrown),
1479
+ ]
1480
+ }
1481
+ return [result.value, null]
1482
+ },
1483
+ }
1484
+ }
1485
+
1486
+ function errorFromUnknown(err: unknown): $.GoError {
1487
+ const message =
1488
+ typeof err === 'object' && err != null && 'message' in err ?
1489
+ String((err as { message: unknown }).message)
1490
+ : String(err)
1491
+ return errors.New(message)
1492
+ }
1493
+
1494
+ async function readFetchBody(
1495
+ fetched: globalThis.Response,
1496
+ requestContext: context.Context,
1497
+ abortFetch: () => void,
1498
+ ): Promise<[Uint8Array, $.GoError]> {
1499
+ const fetchContext = newFetchContext(requestContext)
1500
+ const [buffer, err] = await fetchContext.wait(fetched.arrayBuffer())
1501
+ fetchContext.stop()
1502
+ if (err != null) {
1503
+ abortFetch()
1504
+ return [new Uint8Array(), err]
1224
1505
  }
1506
+ return [new Uint8Array(buffer ?? new ArrayBuffer(0)), null]
1225
1507
  }
1226
1508
 
1509
+ type maybePromise<T> = T | Promise<T>
1510
+
1227
1511
  export interface FileSystem {
1228
- Open(name: string): [File | null, $.GoError]
1512
+ Open(name: string): maybePromise<[File | null, $.GoError]>
1229
1513
  }
1230
1514
 
1231
1515
  export interface File extends io.Closer, io.Reader, io.Seeker {
1232
- Readdir(count: number): [$.Slice<fs.FileInfo>, $.GoError]
1233
- Stat(): [fs.FileInfo, $.GoError]
1516
+ Readdir(count: number): [$.Slice<fs.FileInfo> | null, $.GoError]
1517
+ Stat(): [fs.FileInfo | null, $.GoError]
1518
+ }
1519
+
1520
+ interface fileServerFileSystem {
1521
+ Open(name: string): maybePromise<[fileServerFile | null, $.GoError]>
1522
+ }
1523
+
1524
+ interface fileServerFile {
1525
+ Close(): maybePromise<$.GoError>
1526
+ Read(p: $.Bytes): maybePromise<[number, $.GoError]>
1527
+ Seek(offset: number, whence: number): maybePromise<[number, $.GoError]>
1528
+ Readdir(count: number): maybePromise<[$.Slice<fs.FileInfo> | null, $.GoError]>
1529
+ Stat(): maybePromise<[fs.FileInfo | null, $.GoError]>
1234
1530
  }
1235
1531
 
1236
1532
  export function FS(fsys: fs.FS): FileSystem {
1237
1533
  return {
1238
- Open(name: string): [File | null, $.GoError] {
1534
+ async Open(name: string): Promise<[File | null, $.GoError]> {
1239
1535
  const cleaned = cleanFileServerPath(name)
1240
- const [file, err] = fsys?.Open(cleaned) ?? [null, fs.ErrInvalid]
1536
+ const [file, err] = (await fsys?.Open(cleaned)) ?? [null, fs.ErrInvalid]
1241
1537
  if (err != null || file == null) {
1242
1538
  return [null, err]
1243
1539
  }
@@ -1248,17 +1544,23 @@ export function FS(fsys: fs.FS): FileSystem {
1248
1544
 
1249
1545
  function httpFileFromFSFile(file: Exclude<fs.File, null>): File {
1250
1546
  const seek = (file as Partial<io.Seeker>).Seek
1251
- const readdir = (file as { Readdir?: (count: number) => [$.Slice<fs.FileInfo>, $.GoError] }).Readdir
1547
+ const readdir = (
1548
+ file as { Readdir?: (count: number) => [$.Slice<fs.FileInfo>, $.GoError] }
1549
+ ).Readdir
1252
1550
  return {
1253
- Read: (p) => file.Read(p instanceof Uint8Array ? p : Uint8Array.from(p ?? [])),
1551
+ Read: (p) =>
1552
+ file.Read(p instanceof Uint8Array ? p : Uint8Array.from(p ?? [])),
1254
1553
  Close: () => file.Close(),
1255
1554
  Stat: () => file.Stat(),
1256
- Seek: seek == null ? () => [0, errors.New('net/http: file does not support seek')] : seek.bind(file),
1555
+ Seek:
1556
+ seek == null ?
1557
+ () => [0, errors.New('net/http: file does not support seek')]
1558
+ : seek.bind(file),
1257
1559
  Readdir: readdir == null ? () => [null, io.EOF] : readdir.bind(file),
1258
1560
  }
1259
1561
  }
1260
1562
 
1261
- export function FileServer(root: FileSystem | null): Handler {
1563
+ export function FileServer(root: fileServerFileSystem | null): Handler {
1262
1564
  return {
1263
1565
  async ServeHTTP(w, r): Promise<void> {
1264
1566
  const req = $.pointerValue<Request | null>(r)
@@ -1269,13 +1571,15 @@ export function FileServer(root: FileSystem | null): Handler {
1269
1571
  Error(w, 'method not allowed', StatusMethodNotAllowed)
1270
1572
  return
1271
1573
  }
1272
- const [file, err] = root?.Open(cleanFileServerPath(req.URL?.Path ?? '')) ?? [null, fs.ErrInvalid]
1574
+ const [file, err] = (await root?.Open(
1575
+ cleanFileServerPath(req.URL?.Path ?? ''),
1576
+ )) ?? [null, fs.ErrInvalid]
1273
1577
  if (err != null || file == null) {
1274
1578
  NotFound(w, req)
1275
1579
  return
1276
1580
  }
1277
1581
  try {
1278
- const [info, statErr] = file.Stat()
1582
+ const [info, statErr] = await file.Stat()
1279
1583
  if (statErr != null) {
1280
1584
  Error(w, statErr.Error(), StatusInternalServerError)
1281
1585
  return
@@ -1284,20 +1588,30 @@ export function FileServer(root: FileSystem | null): Handler {
1284
1588
  NotFound(w, req)
1285
1589
  return
1286
1590
  }
1287
- const [data, readErr] = await io.ReadAll(file)
1288
- if (readErr != null) {
1289
- Error(w, readErr.Error(), StatusInternalServerError)
1290
- return
1591
+ const header = await w.Header()
1592
+ if (Header_Get(header, 'Content-Type') === '') {
1593
+ const contentType = mime.TypeByExtension(
1594
+ path.Ext(info?.Name?.() || req.URL?.Path || ''),
1595
+ )
1596
+ if (contentType !== '') {
1597
+ Header_Set(header, 'Content-Type', contentType)
1598
+ }
1291
1599
  }
1292
1600
  if (info?.Size != null) {
1293
- Header_Set(w.Header(), 'Content-Length', String(info.Size()))
1601
+ Header_Set(header, 'Content-Length', String(info.Size()))
1294
1602
  }
1295
- w.WriteHeader(StatusOK)
1603
+ await w.WriteHeader(StatusOK)
1296
1604
  if (req.Method !== MethodHead) {
1297
- w.Write(data)
1605
+ const [, copyErr] = await io.Copy(
1606
+ w as unknown as io.Writer,
1607
+ file as io.Reader,
1608
+ )
1609
+ if (copyErr != null) {
1610
+ return
1611
+ }
1298
1612
  }
1299
1613
  } finally {
1300
- file.Close()
1614
+ await file.Close()
1301
1615
  }
1302
1616
  },
1303
1617
  }
@@ -1348,8 +1662,25 @@ function cleanFileServerPath(name: string): string {
1348
1662
  }
1349
1663
 
1350
1664
  export interface Handler {
1351
- ServeHTTP(w: ResponseWriter | null, r: Request | $.VarRef<Request> | null): void | Promise<void>
1352
- }
1665
+ ServeHTTP(
1666
+ w: ResponseWriter | null,
1667
+ r: Request | $.VarRef<Request> | null,
1668
+ ): void | Promise<void>
1669
+ }
1670
+
1671
+ $.registerInterfaceType('http.Handler', null, [
1672
+ {
1673
+ name: 'ServeHTTP',
1674
+ args: [
1675
+ { name: 'w', type: 'http.ResponseWriter' },
1676
+ {
1677
+ name: 'r',
1678
+ type: { kind: $.TypeKind.Pointer, elemType: 'http.Request' },
1679
+ },
1680
+ ],
1681
+ returns: [],
1682
+ },
1683
+ ])
1353
1684
 
1354
1685
  export type HandlerFunc = (
1355
1686
  w: ResponseWriter | null,
@@ -1357,10 +1688,13 @@ export type HandlerFunc = (
1357
1688
  ) => void | Promise<void>
1358
1689
 
1359
1690
  export function HandlerFunc_ServeHTTP(
1360
- h: HandlerFunc,
1691
+ h: HandlerFunc | null,
1361
1692
  w: ResponseWriter | null,
1362
1693
  r: Request | $.VarRef<Request> | null,
1363
1694
  ): void | Promise<void> {
1695
+ if (!h) {
1696
+ throw new globalThis.Error('http: nil HandlerFunc')
1697
+ }
1364
1698
  return h(w, r)
1365
1699
  }
1366
1700
 
@@ -1512,7 +1846,9 @@ function wildcardPatternMatches(pattern: string, path: string): boolean {
1512
1846
  export class Server {
1513
1847
  public Addr: string
1514
1848
  public BaseContext: ((listener: any) => context.Context) | null
1515
- public ConnContext: ((ctx: context.Context, conn: any) => context.Context) | null
1849
+ public ConnContext:
1850
+ | ((ctx: context.Context, conn: any) => context.Context)
1851
+ | null
1516
1852
  public Handler: Handler | null
1517
1853
  public DisableGeneralOptionsHandler: boolean
1518
1854
  public TLSConfig: any
@@ -1533,7 +1869,8 @@ export class Server {
1533
1869
  this.BaseContext = init?.BaseContext ?? null
1534
1870
  this.ConnContext = init?.ConnContext ?? null
1535
1871
  this.Handler = init?.Handler ?? null
1536
- this.DisableGeneralOptionsHandler = init?.DisableGeneralOptionsHandler ?? false
1872
+ this.DisableGeneralOptionsHandler =
1873
+ init?.DisableGeneralOptionsHandler ?? false
1537
1874
  this.TLSConfig = init?.TLSConfig ?? null
1538
1875
  this.ReadTimeout = init?.ReadTimeout ?? 0
1539
1876
  this.ReadTimeoutHandler = (init as any)?.ReadTimeoutHandler ?? null
@@ -1549,11 +1886,15 @@ export class Server {
1549
1886
  }
1550
1887
 
1551
1888
  public ListenAndServe(): $.GoError {
1552
- return errors.New('net/http: Server.ListenAndServe is not implemented in GoScript')
1889
+ return errors.New(
1890
+ 'net/http: Server.ListenAndServe is not implemented in GoScript',
1891
+ )
1553
1892
  }
1554
1893
 
1555
1894
  public ListenAndServeTLS(_certFile: string, _keyFile: string): $.GoError {
1556
- return errors.New('net/http: Server.ListenAndServeTLS is not implemented in GoScript')
1895
+ return errors.New(
1896
+ 'net/http: Server.ListenAndServeTLS is not implemented in GoScript',
1897
+ )
1557
1898
  }
1558
1899
 
1559
1900
  public Close(): $.GoError {
@@ -1568,11 +1909,18 @@ export class Server {
1568
1909
  return ErrNotSupported
1569
1910
  }
1570
1911
 
1571
- public ServeTLS(_listener: any, _certFile: string, _keyFile: string): $.GoError {
1912
+ public ServeTLS(
1913
+ _listener: any,
1914
+ _certFile: string,
1915
+ _keyFile: string,
1916
+ ): $.GoError {
1572
1917
  return ErrNotSupported
1573
1918
  }
1574
1919
 
1575
- public ServeHTTP(w: ResponseWriter | null, r: Request | $.VarRef<Request> | null): void | Promise<void> {
1920
+ public ServeHTTP(
1921
+ w: ResponseWriter | null,
1922
+ r: Request | $.VarRef<Request> | null,
1923
+ ): void | Promise<void> {
1576
1924
  return (this.Handler ?? DefaultServeMux).ServeHTTP(w, r)
1577
1925
  }
1578
1926
 
@@ -1581,7 +1929,10 @@ export class Server {
1581
1929
  public SetKeepAlivesEnabled(_v: boolean): void {}
1582
1930
  }
1583
1931
 
1584
- export function ListenAndServe(_addr: string, _handler: Handler | null): $.GoError {
1932
+ export function ListenAndServe(
1933
+ _addr: string,
1934
+ _handler: Handler | null,
1935
+ ): $.GoError {
1585
1936
  return ErrNotSupported
1586
1937
  }
1587
1938
 
@@ -1624,7 +1975,10 @@ export interface Hijacker {
1624
1975
  }
1625
1976
 
1626
1977
  export interface Pusher {
1627
- Push(target: string, opts: PushOptions | $.VarRef<PushOptions> | null): $.GoError
1978
+ Push(
1979
+ target: string,
1980
+ opts: PushOptions | $.VarRef<PushOptions> | null,
1981
+ ): $.GoError
1628
1982
  }
1629
1983
 
1630
1984
  export class ResponseController {
@@ -1657,7 +2011,9 @@ export class ResponseController {
1657
2011
  }
1658
2012
  }
1659
2013
 
1660
- export function NewResponseController(rw: ResponseWriter | null): ResponseController {
2014
+ export function NewResponseController(
2015
+ rw: ResponseWriter | null,
2016
+ ): ResponseController {
1661
2017
  return new ResponseController(rw)
1662
2018
  }
1663
2019
 
@@ -1666,7 +2022,10 @@ class maxBytesReader implements io.ReadCloser {
1666
2022
  private remaining: number
1667
2023
  private err: $.GoError = null
1668
2024
 
1669
- constructor(private reader: io.ReadCloser, limit: number) {
2025
+ constructor(
2026
+ private reader: io.ReadCloser,
2027
+ limit: number,
2028
+ ) {
1670
2029
  this.initialLimit = Math.max(0, limit)
1671
2030
  this.remaining = this.initialLimit
1672
2031
  }
@@ -1719,14 +2078,19 @@ export class ServeMux implements Handler {
1719
2078
  this.Handle(pattern, { ServeHTTP: handler })
1720
2079
  }
1721
2080
 
1722
- public Handler(r: Request | $.VarRef<Request> | null): [Handler | null, string] {
2081
+ public Handler(
2082
+ r: Request | $.VarRef<Request> | null,
2083
+ ): [Handler | null, string] {
1723
2084
  const req = $.pointerValue<Request | null>(r)
1724
2085
  const path = req?.URL?.Path ?? ''
1725
2086
  const handler = this.handlers.get(path) ?? null
1726
2087
  return [handler, handler == null ? '' : path]
1727
2088
  }
1728
2089
 
1729
- public ServeHTTP(w: ResponseWriter | null, r: Request | $.VarRef<Request> | null): void | Promise<void> {
2090
+ public ServeHTTP(
2091
+ w: ResponseWriter | null,
2092
+ r: Request | $.VarRef<Request> | null,
2093
+ ): void | Promise<void> {
1730
2094
  const [handler] = this.Handler(r)
1731
2095
  if (handler == null) {
1732
2096
  NotFound(w, r)
@@ -1754,7 +2118,11 @@ export function StripPrefix(prefix: string, handler: Handler | null): Handler {
1754
2118
  return {
1755
2119
  ServeHTTP(w, r) {
1756
2120
  const req = $.pointerValue<Request | null>(r)
1757
- if (req?.URL != null && typeof req.URL.Path === 'string' && req.URL.Path.startsWith(prefix)) {
2121
+ if (
2122
+ req?.URL != null &&
2123
+ typeof req.URL.Path === 'string' &&
2124
+ req.URL.Path.startsWith(prefix)
2125
+ ) {
1758
2126
  req.URL = { ...req.URL, Path: req.URL.Path.slice(prefix.length) || '/' }
1759
2127
  }
1760
2128
  return handler?.ServeHTTP(w, req)
@@ -1791,12 +2159,16 @@ export function NotFoundHandler(): Handler {
1791
2159
  export function RedirectHandler(url: string, code: number): Handler {
1792
2160
  return {
1793
2161
  ServeHTTP(w, r) {
1794
- Redirect(w, r, url, code)
2162
+ return Redirect(w, r, url, code)
1795
2163
  },
1796
2164
  }
1797
2165
  }
1798
2166
 
1799
- export function TimeoutHandler(handler: Handler | null, _dt: number, msg: string): Handler {
2167
+ export function TimeoutHandler(
2168
+ handler: Handler | null,
2169
+ _dt: number,
2170
+ msg: string,
2171
+ ): Handler {
1800
2172
  return {
1801
2173
  ServeHTTP(w, r) {
1802
2174
  if (handler == null) {
@@ -1808,32 +2180,45 @@ export function TimeoutHandler(handler: Handler | null, _dt: number, msg: string
1808
2180
  }
1809
2181
  }
1810
2182
 
1811
- export function Error(w: ResponseWriter | null, error: string, code: number): void {
2183
+ export function Error(
2184
+ w: ResponseWriter | null,
2185
+ error: string,
2186
+ code: number,
2187
+ ): void {
1812
2188
  w?.WriteHeader(code)
1813
2189
  w?.Write($.stringToBytes(error + '\n'))
1814
2190
  }
1815
2191
 
1816
- export function NotFound(w: ResponseWriter | null, _r: Request | $.VarRef<Request> | null): void {
2192
+ export function NotFound(
2193
+ w: ResponseWriter | null,
2194
+ _r: Request | $.VarRef<Request> | null,
2195
+ ): void {
1817
2196
  Error(w, '404 page not found', StatusNotFound)
1818
2197
  }
1819
2198
 
1820
- export function Redirect(
2199
+ export async function Redirect(
1821
2200
  w: ResponseWriter | null,
1822
2201
  _r: Request | $.VarRef<Request> | null,
1823
2202
  url: string,
1824
2203
  code: number,
1825
- ): void {
1826
- const header = w?.Header()
2204
+ ): Promise<void> {
2205
+ if (w == null) {
2206
+ return
2207
+ }
2208
+ const header = await w.Header()
1827
2209
  if (header != null) {
1828
2210
  Header_Set(header, 'Location', url)
1829
2211
  }
1830
- w?.WriteHeader(code)
2212
+ await w.WriteHeader(code)
1831
2213
  }
1832
2214
 
1833
2215
  export function ParseTime(text: string): [time.Time, $.GoError] {
1834
2216
  const date = new globalThis.Date(text)
1835
2217
  if (isNaN(date.getTime())) {
1836
- return [new time.Time(), $.newError(`parsing time "${text}" as HTTP-date: cannot parse`)]
2218
+ return [
2219
+ new time.Time(),
2220
+ $.newError(`parsing time "${text}" as HTTP-date: cannot parse`),
2221
+ ]
1837
2222
  }
1838
2223
  return [time.UnixMilli(date.getTime()), null]
1839
2224
  }
@@ -1881,7 +2266,10 @@ export function DetectContentType(data: $.Slice<number>): string {
1881
2266
  if (startsWithBytes(bytes, new Uint8Array([0xef, 0xbb, 0xbf]))) {
1882
2267
  return 'text/plain; charset=utf-8'
1883
2268
  }
1884
- if (startsWithBytes(bytes, new Uint8Array([0x00, 0x00, 0x01, 0x00])) || startsWithBytes(bytes, new Uint8Array([0x00, 0x00, 0x02, 0x00]))) {
2269
+ if (
2270
+ startsWithBytes(bytes, new Uint8Array([0x00, 0x00, 0x01, 0x00])) ||
2271
+ startsWithBytes(bytes, new Uint8Array([0x00, 0x00, 0x02, 0x00]))
2272
+ ) {
1885
2273
  return 'image/x-icon'
1886
2274
  }
1887
2275
  if (startsWithASCII(bytes, 'BM')) {
@@ -1893,7 +2281,12 @@ export function DetectContentType(data: $.Slice<number>): string {
1893
2281
  if (isRIFFSignature(bytes, 'WEBPVP')) {
1894
2282
  return 'image/webp'
1895
2283
  }
1896
- if (startsWithBytes(bytes, new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))) {
2284
+ if (
2285
+ startsWithBytes(
2286
+ bytes,
2287
+ new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
2288
+ )
2289
+ ) {
1897
2290
  return 'image/png'
1898
2291
  }
1899
2292
  if (startsWithBytes(bytes, new Uint8Array([0xff, 0xd8, 0xff]))) {
@@ -1908,7 +2301,12 @@ export function DetectContentType(data: $.Slice<number>): string {
1908
2301
  if (startsWithBytes(bytes, new Uint8Array([0x4f, 0x67, 0x67, 0x53, 0x00]))) {
1909
2302
  return 'application/ogg'
1910
2303
  }
1911
- if (startsWithBytes(bytes, new Uint8Array([0x4d, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06]))) {
2304
+ if (
2305
+ startsWithBytes(
2306
+ bytes,
2307
+ new Uint8Array([0x4d, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06]),
2308
+ )
2309
+ ) {
1912
2310
  return 'audio/midi'
1913
2311
  }
1914
2312
  if (isRIFFSignature(bytes, 'AVI ')) {
@@ -1947,17 +2345,32 @@ export function DetectContentType(data: $.Slice<number>): string {
1947
2345
  if (startsWithBytes(bytes, new Uint8Array([0x50, 0x4b, 0x03, 0x04]))) {
1948
2346
  return 'application/zip'
1949
2347
  }
1950
- if (startsWithBytes(bytes, new Uint8Array([0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00]))) {
2348
+ if (
2349
+ startsWithBytes(
2350
+ bytes,
2351
+ new Uint8Array([0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00]),
2352
+ )
2353
+ ) {
1951
2354
  return 'application/x-rar-compressed'
1952
2355
  }
1953
- if (startsWithBytes(bytes, new Uint8Array([0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x01, 0x00]))) {
2356
+ if (
2357
+ startsWithBytes(
2358
+ bytes,
2359
+ new Uint8Array([0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x01, 0x00]),
2360
+ )
2361
+ ) {
1954
2362
  return 'application/x-rar-compressed'
1955
2363
  }
1956
2364
  if (startsWithBytes(bytes, new Uint8Array([0x00, 0x61, 0x73, 0x6d]))) {
1957
2365
  return 'application/wasm'
1958
2366
  }
1959
2367
  for (const byte of afterWS) {
1960
- if (byte <= 0x08 || byte === 0x0b || (byte >= 0x0e && byte <= 0x1a) || (byte >= 0x1c && byte <= 0x1f)) {
2368
+ if (
2369
+ byte <= 0x08 ||
2370
+ byte === 0x0b ||
2371
+ (byte >= 0x0e && byte <= 0x1a) ||
2372
+ (byte >= 0x1c && byte <= 0x1f)
2373
+ ) {
1961
2374
  return 'application/octet-stream'
1962
2375
  }
1963
2376
  }
@@ -1967,7 +2380,13 @@ export function DetectContentType(data: $.Slice<number>): string {
1967
2380
  function firstNonWhitespace(data: Uint8Array): number {
1968
2381
  for (let i = 0; i < data.length; i++) {
1969
2382
  const byte = data[i]
1970
- if (byte !== 0x09 && byte !== 0x0a && byte !== 0x0c && byte !== 0x0d && byte !== 0x20) {
2383
+ if (
2384
+ byte !== 0x09 &&
2385
+ byte !== 0x0a &&
2386
+ byte !== 0x0c &&
2387
+ byte !== 0x0d &&
2388
+ byte !== 0x20
2389
+ ) {
1971
2390
  return i
1972
2391
  }
1973
2392
  }
@@ -1990,7 +2409,11 @@ function startsWithASCII(data: Uint8Array, prefix: string): boolean {
1990
2409
  return asciiMatchesAt(data, 0, prefix)
1991
2410
  }
1992
2411
 
1993
- function asciiMatchesAt(data: Uint8Array, offset: number, text: string): boolean {
2412
+ function asciiMatchesAt(
2413
+ data: Uint8Array,
2414
+ offset: number,
2415
+ text: string,
2416
+ ): boolean {
1994
2417
  if (data.length < offset + text.length) {
1995
2418
  return false
1996
2419
  }
@@ -2102,7 +2525,12 @@ export function ParseSetCookie(line: string): [Cookie | null, $.GoError] {
2102
2525
  if (!ok) {
2103
2526
  return [null, errInvalidCookieValue]
2104
2527
  }
2105
- const cookie = new Cookie({ Name: name, Value: value, Quoted: quoted, Raw: line })
2528
+ const cookie = new Cookie({
2529
+ Name: name,
2530
+ Value: value,
2531
+ Quoted: quoted,
2532
+ Raw: line,
2533
+ })
2106
2534
  const unparsed: string[] = []
2107
2535
  for (const raw of parts.slice(1)) {
2108
2536
  const part = raw.trim()
@@ -2158,7 +2586,10 @@ export function ParseSetCookie(line: string): [Cookie | null, $.GoError] {
2158
2586
  break
2159
2587
  }
2160
2588
  let secs = Number.parseInt(attrValue, 10)
2161
- if ((secs !== 0 && attrValue[0] === '0') || !Number.isSafeInteger(secs)) {
2589
+ if (
2590
+ (secs !== 0 && attrValue[0] === '0') ||
2591
+ !Number.isSafeInteger(secs)
2592
+ ) {
2162
2593
  break
2163
2594
  }
2164
2595
  if (secs <= 0) {
@@ -2207,7 +2638,10 @@ export function NewRequestWithContext(
2207
2638
  method = MethodGet
2208
2639
  }
2209
2640
  if (!isToken(method)) {
2210
- return [null, errors.New(`net/http: invalid method ${JSON.stringify(method)}`)]
2641
+ return [
2642
+ null,
2643
+ errors.New(`net/http: invalid method ${JSON.stringify(method)}`),
2644
+ ]
2211
2645
  }
2212
2646
  if (ctx == null) {
2213
2647
  return [null, errors.New('net/http: nil Context')]
@@ -2217,7 +2651,17 @@ export function NewRequestWithContext(
2217
2651
  return [null, err]
2218
2652
  }
2219
2653
  const bodyInfo = requestBodyInfo(body, 0)
2220
- return [new Request({ Method: method, URL: parsedURL, Body: bodyInfo.Body, ContentLength: bodyInfo.ContentLength, Host: parsedURL.Host, ctx }), null]
2654
+ return [
2655
+ new Request({
2656
+ Method: method,
2657
+ URL: parsedURL,
2658
+ Body: bodyInfo.Body,
2659
+ ContentLength: bodyInfo.ContentLength,
2660
+ Host: parsedURL.Host,
2661
+ ctx,
2662
+ }),
2663
+ null,
2664
+ ]
2221
2665
  }
2222
2666
 
2223
2667
  export async function Get(_url: string): Promise<[Response | null, $.GoError]> {
@@ -2228,7 +2672,9 @@ export async function Get(_url: string): Promise<[Response | null, $.GoError]> {
2228
2672
  return await DefaultClient.Do(req)
2229
2673
  }
2230
2674
 
2231
- export async function Head(_url: string): Promise<[Response | null, $.GoError]> {
2675
+ export async function Head(
2676
+ _url: string,
2677
+ ): Promise<[Response | null, $.GoError]> {
2232
2678
  const [req, err] = NewRequest(MethodHead, _url, null)
2233
2679
  if (err != null) {
2234
2680
  return [null, err]
@@ -2249,15 +2695,22 @@ export async function Post(
2249
2695
  return await DefaultClient.Do(req)
2250
2696
  }
2251
2697
 
2252
- export async function PostForm(_url: string, data: any): Promise<[Response | null, $.GoError]> {
2698
+ export async function PostForm(
2699
+ _url: string,
2700
+ data: any,
2701
+ ): Promise<[Response | null, $.GoError]> {
2253
2702
  return await DefaultClient.PostForm(_url, data)
2254
2703
  }
2255
2704
 
2256
- export function ProxyFromEnvironment(_req: Request | $.VarRef<Request> | null): [any, $.GoError] {
2705
+ export function ProxyFromEnvironment(
2706
+ _req: Request | $.VarRef<Request> | null,
2707
+ ): [any, $.GoError] {
2257
2708
  return [null, null]
2258
2709
  }
2259
2710
 
2260
- export function ProxyURL(fixedURL: any): (req: Request | $.VarRef<Request> | null) => [any, $.GoError] {
2711
+ export function ProxyURL(
2712
+ fixedURL: any,
2713
+ ): (req: Request | $.VarRef<Request> | null) => [any, $.GoError] {
2261
2714
  return () => [fixedURL, null]
2262
2715
  }
2263
2716
 
@@ -2265,7 +2718,10 @@ export function ReadRequest(_reader: any): [Request | null, $.GoError] {
2265
2718
  return [null, ErrNotSupported]
2266
2719
  }
2267
2720
 
2268
- export function ReadResponse(_reader: any, _req: Request | $.VarRef<Request> | null): [Response | null, $.GoError] {
2721
+ export function ReadResponse(
2722
+ _reader: any,
2723
+ _req: Request | $.VarRef<Request> | null,
2724
+ ): [Response | null, $.GoError] {
2269
2725
  return [null, ErrNotSupported]
2270
2726
  }
2271
2727
 
@@ -2303,7 +2759,10 @@ function readCloserForBody(body: io.Reader | null): io.ReadCloser | null {
2303
2759
  return io.NopCloser(body)
2304
2760
  }
2305
2761
 
2306
- function requestBodyInfo(body: io.Reader | null, unknownLength: number): { Body: io.ReadCloser | null; ContentLength: number } {
2762
+ function requestBodyInfo(
2763
+ body: io.Reader | null,
2764
+ unknownLength: number,
2765
+ ): { Body: io.ReadCloser | null; ContentLength: number } {
2307
2766
  if (body == null) {
2308
2767
  return { Body: null, ContentLength: 0 }
2309
2768
  }
@@ -2311,9 +2770,16 @@ function requestBodyInfo(body: io.Reader | null, unknownLength: number): { Body:
2311
2770
  if (value === NoBody) {
2312
2771
  return { Body: NoBody, ContentLength: 0 }
2313
2772
  }
2314
- if (value instanceof bytes.Buffer || value instanceof bytes.Reader || value instanceof strings.Reader) {
2773
+ if (
2774
+ value instanceof bytes.Buffer ||
2775
+ value instanceof bytes.Reader ||
2776
+ value instanceof strings.Reader
2777
+ ) {
2315
2778
  const length = value.Len()
2316
- return { Body: length === 0 ? NoBody : readCloserForBody(body), ContentLength: length }
2779
+ return {
2780
+ Body: length === 0 ? NoBody : readCloserForBody(body),
2781
+ ContentLength: length,
2782
+ }
2317
2783
  }
2318
2784
  return { Body: readCloserForBody(body), ContentLength: unknownLength }
2319
2785
  }