goscript 0.2.3 → 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/lowering.go +57 -8
- package/compiler/override-registry_test.go +125 -0
- package/compiler/skeleton_test.go +49 -2
- 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/io/fs/glob.js +1 -1
- package/dist/gs/io/fs/glob.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 +7 -7
- package/dist/gs/io/fs/sub.js.map +1 -1
- package/dist/gs/io/fs/walk.js +1 -1
- package/dist/gs/io/fs/walk.js.map +1 -1
- package/dist/gs/net/http/index.d.ts +18 -14
- package/dist/gs/net/http/index.js +44 -23
- 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/gs/builtin/runtime-contract.test.ts +25 -0
- 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/embed/index.test.ts +1 -1
- package/gs/io/fs/glob.ts +1 -1
- package/gs/io/fs/meta.json +3 -0
- 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 +9 -9
- package/gs/io/fs/walk.ts +1 -1
- package/gs/net/http/index.test.ts +207 -2
- package/gs/net/http/index.ts +68 -37
- 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 +30 -27
- package/package.json +1 -1
package/gs/io/fs/walk.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
ErrServerClosed,
|
|
21
21
|
File,
|
|
22
22
|
FileServer,
|
|
23
|
+
FileServerFS,
|
|
23
24
|
FileSystem,
|
|
24
25
|
FS,
|
|
25
26
|
Get,
|
|
@@ -192,6 +193,36 @@ describe('net/http override', () => {
|
|
|
192
193
|
expect(protocols.String()).toBe('{HTTP1,UnencryptedHTTP2}')
|
|
193
194
|
})
|
|
194
195
|
|
|
196
|
+
it('accepts pointer-wrapped headers from generated ResponseWriter methods', () => {
|
|
197
|
+
const header = varRef(new Header())
|
|
198
|
+
|
|
199
|
+
Header_Set(header, 'content-length', '12')
|
|
200
|
+
Header_Add(header, 'content-type', 'text/plain')
|
|
201
|
+
|
|
202
|
+
expect(Header_Get(header, 'Content-Length')).toBe('12')
|
|
203
|
+
expect(Array.from(Header_Values(header, 'Content-Type') ?? [])).toEqual([
|
|
204
|
+
'text/plain',
|
|
205
|
+
])
|
|
206
|
+
|
|
207
|
+
const cloned = Header_Clone(header)
|
|
208
|
+
Header_Del(header, 'Content-Type')
|
|
209
|
+
|
|
210
|
+
expect(Header_Get(header, 'Content-Type')).toBe('')
|
|
211
|
+
expect(Header_Get(cloned, 'Content-Type')).toBe('text/plain')
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('accepts interface-boxed named headers from generated interface calls', () => {
|
|
215
|
+
const header = $.namedValueInterfaceValue(new Header(), 'http.Header', {})
|
|
216
|
+
|
|
217
|
+
Header_Set(header, 'content-length', '12')
|
|
218
|
+
Header_Add(header, 'content-type', 'text/plain')
|
|
219
|
+
|
|
220
|
+
expect(Header_Get(header, 'Content-Length')).toBe('12')
|
|
221
|
+
expect(Array.from(Header_Values(header, 'Content-Type') ?? [])).toEqual([
|
|
222
|
+
'text/plain',
|
|
223
|
+
])
|
|
224
|
+
})
|
|
225
|
+
|
|
195
226
|
it('validates outgoing request construction', () => {
|
|
196
227
|
const [req, reqErr] = NewRequestWithContext(
|
|
197
228
|
context.Background(),
|
|
@@ -1171,7 +1202,7 @@ describe('net/http override', () => {
|
|
|
1171
1202
|
])
|
|
1172
1203
|
})
|
|
1173
1204
|
|
|
1174
|
-
it('formats Set-Cookie headers for browser bootstrap routes', () => {
|
|
1205
|
+
it('formats Set-Cookie headers for browser bootstrap routes', async () => {
|
|
1175
1206
|
const header = new Header()
|
|
1176
1207
|
const writer: ResponseWriter = {
|
|
1177
1208
|
Header: () => header,
|
|
@@ -1179,7 +1210,7 @@ describe('net/http override', () => {
|
|
|
1179
1210
|
WriteHeader: () => undefined,
|
|
1180
1211
|
}
|
|
1181
1212
|
|
|
1182
|
-
SetCookie(
|
|
1213
|
+
await SetCookie(
|
|
1183
1214
|
writer,
|
|
1184
1215
|
new Cookie({
|
|
1185
1216
|
Name: 'spacewave_local_capability',
|
|
@@ -1314,6 +1345,7 @@ describe('net/http override', () => {
|
|
|
1314
1345
|
expect(opened).toEqual(['file.txt'])
|
|
1315
1346
|
expect(writes).toEqual(['status:200', 'hello'])
|
|
1316
1347
|
expect(Header_Get(header, 'Content-Length')).toBe('5')
|
|
1348
|
+
expect(Header_Get(header, 'Content-Type')).toBe('text/plain; charset=utf-8')
|
|
1317
1349
|
|
|
1318
1350
|
writes.length = 0
|
|
1319
1351
|
opened.length = 0
|
|
@@ -1399,6 +1431,179 @@ describe('net/http override', () => {
|
|
|
1399
1431
|
expect(closeCalls).toEqual(['async.txt'])
|
|
1400
1432
|
})
|
|
1401
1433
|
|
|
1434
|
+
it('serves files through FS adapters backed by async io/fs Open', async () => {
|
|
1435
|
+
const opened: string[] = []
|
|
1436
|
+
const root = {
|
|
1437
|
+
async Open(name: string) {
|
|
1438
|
+
opened.push(name)
|
|
1439
|
+
const data = $.stringToBytes('fs-adapter')
|
|
1440
|
+
let offset = 0
|
|
1441
|
+
return [
|
|
1442
|
+
{
|
|
1443
|
+
Close: async () => null,
|
|
1444
|
+
Read: async (p: $.Slice<number>) => {
|
|
1445
|
+
if (offset >= data.length) {
|
|
1446
|
+
return [0, io.EOF] as [number, $.GoError]
|
|
1447
|
+
}
|
|
1448
|
+
const n = Math.min(p?.length ?? 0, data.length - offset)
|
|
1449
|
+
p?.set(data.subarray(offset, offset + n), 0)
|
|
1450
|
+
offset += n
|
|
1451
|
+
return [n, null] as [number, $.GoError]
|
|
1452
|
+
},
|
|
1453
|
+
Stat: async () => [
|
|
1454
|
+
{
|
|
1455
|
+
Name: () => name,
|
|
1456
|
+
Size: () => data.length,
|
|
1457
|
+
Mode: () => 0,
|
|
1458
|
+
ModTime: () => new time.Time(),
|
|
1459
|
+
IsDir: () => false,
|
|
1460
|
+
Sys: () => null,
|
|
1461
|
+
},
|
|
1462
|
+
null,
|
|
1463
|
+
],
|
|
1464
|
+
},
|
|
1465
|
+
null,
|
|
1466
|
+
]
|
|
1467
|
+
},
|
|
1468
|
+
}
|
|
1469
|
+
const writes: string[] = []
|
|
1470
|
+
const header = new Header()
|
|
1471
|
+
const writer: ResponseWriter = {
|
|
1472
|
+
Header: () => header,
|
|
1473
|
+
Write: (p) => {
|
|
1474
|
+
writes.push(Buffer.from(p ?? []).toString('utf8'))
|
|
1475
|
+
return [p?.length ?? 0, null]
|
|
1476
|
+
},
|
|
1477
|
+
WriteHeader: (code) => writes.push(`status:${code}`),
|
|
1478
|
+
}
|
|
1479
|
+
const [req] = NewRequest(
|
|
1480
|
+
MethodGet,
|
|
1481
|
+
'http://example.invalid/dir/fs-adapter.txt',
|
|
1482
|
+
null,
|
|
1483
|
+
)
|
|
1484
|
+
|
|
1485
|
+
await FileServerFS(root).ServeHTTP(writer, req)
|
|
1486
|
+
|
|
1487
|
+
expect(opened).toEqual(['dir/fs-adapter.txt'])
|
|
1488
|
+
expect(writes).toEqual(['status:200', 'fs-adapter'])
|
|
1489
|
+
expect(Header_Get(header, 'Content-Length')).toBe('10')
|
|
1490
|
+
})
|
|
1491
|
+
|
|
1492
|
+
it('serves files through async response writer adapters', async () => {
|
|
1493
|
+
const root = {
|
|
1494
|
+
async Open(name: string) {
|
|
1495
|
+
const data = $.stringToBytes('async-adapter')
|
|
1496
|
+
return [
|
|
1497
|
+
{
|
|
1498
|
+
Read: (p: $.Slice<number>) => {
|
|
1499
|
+
p?.set(data.subarray(0, p.length))
|
|
1500
|
+
return [data.length, io.EOF] as [number, $.GoError]
|
|
1501
|
+
},
|
|
1502
|
+
Close: async () => null,
|
|
1503
|
+
Stat: async () => [
|
|
1504
|
+
{
|
|
1505
|
+
Name: () => name,
|
|
1506
|
+
Size: () => data.length,
|
|
1507
|
+
Mode: () => 0,
|
|
1508
|
+
ModTime: () => new time.Time(),
|
|
1509
|
+
IsDir: () => false,
|
|
1510
|
+
Sys: () => null,
|
|
1511
|
+
},
|
|
1512
|
+
null,
|
|
1513
|
+
],
|
|
1514
|
+
},
|
|
1515
|
+
null,
|
|
1516
|
+
] as [File, $.GoError]
|
|
1517
|
+
},
|
|
1518
|
+
}
|
|
1519
|
+
const writes: string[] = []
|
|
1520
|
+
const header = new Header()
|
|
1521
|
+
const writer: ResponseWriter = {
|
|
1522
|
+
Header: async () => header,
|
|
1523
|
+
Write: async (p) => {
|
|
1524
|
+
writes.push(Buffer.from(p ?? []).toString('utf8'))
|
|
1525
|
+
return [p?.length ?? 0, null]
|
|
1526
|
+
},
|
|
1527
|
+
WriteHeader: async (code) => {
|
|
1528
|
+
writes.push(`status:${code}`)
|
|
1529
|
+
},
|
|
1530
|
+
}
|
|
1531
|
+
const [req] = NewRequest(
|
|
1532
|
+
MethodGet,
|
|
1533
|
+
'http://example.invalid/async-adapter.txt',
|
|
1534
|
+
null,
|
|
1535
|
+
)
|
|
1536
|
+
|
|
1537
|
+
await FileServer(root).ServeHTTP(writer, req)
|
|
1538
|
+
|
|
1539
|
+
expect(writes).toEqual(['status:200', 'async-adapter'])
|
|
1540
|
+
expect(Header_Get(header, 'Content-Length')).toBe('13')
|
|
1541
|
+
})
|
|
1542
|
+
|
|
1543
|
+
it('writes file server headers before reading response bodies', async () => {
|
|
1544
|
+
let headerWritten = false
|
|
1545
|
+
let readSawHeader = false
|
|
1546
|
+
let offset = 0
|
|
1547
|
+
const data = $.stringToBytes('streamed-body')
|
|
1548
|
+
const root = {
|
|
1549
|
+
async Open(name: string) {
|
|
1550
|
+
return [
|
|
1551
|
+
{
|
|
1552
|
+
Close: async () => null,
|
|
1553
|
+
Read: async (p: $.Slice<number>) => {
|
|
1554
|
+
readSawHeader = headerWritten
|
|
1555
|
+
if (offset >= data.length) {
|
|
1556
|
+
return [0, io.EOF] as [number, $.GoError]
|
|
1557
|
+
}
|
|
1558
|
+
const n = Math.min(p?.length ?? 0, data.length - offset)
|
|
1559
|
+
p?.set(data.subarray(offset, offset + n), 0)
|
|
1560
|
+
offset += n
|
|
1561
|
+
return [n, null] as [number, $.GoError]
|
|
1562
|
+
},
|
|
1563
|
+
Seek: async () => [0, null] as [number, $.GoError],
|
|
1564
|
+
Readdir: async () => [null, null] as [null, $.GoError],
|
|
1565
|
+
Stat: async () => [
|
|
1566
|
+
{
|
|
1567
|
+
Name: () => name,
|
|
1568
|
+
Size: () => data.length,
|
|
1569
|
+
Mode: () => 0,
|
|
1570
|
+
ModTime: () => null as never,
|
|
1571
|
+
IsDir: () => false,
|
|
1572
|
+
Sys: () => null,
|
|
1573
|
+
},
|
|
1574
|
+
null,
|
|
1575
|
+
],
|
|
1576
|
+
},
|
|
1577
|
+
null,
|
|
1578
|
+
] as [File, $.GoError]
|
|
1579
|
+
},
|
|
1580
|
+
}
|
|
1581
|
+
const writes: string[] = []
|
|
1582
|
+
const header = new Header()
|
|
1583
|
+
const writer: ResponseWriter = {
|
|
1584
|
+
Header: () => header,
|
|
1585
|
+
Write: (p) => {
|
|
1586
|
+
writes.push(Buffer.from(p ?? []).toString('utf8'))
|
|
1587
|
+
return [p?.length ?? 0, null]
|
|
1588
|
+
},
|
|
1589
|
+
WriteHeader: (code) => {
|
|
1590
|
+
headerWritten = true
|
|
1591
|
+
writes.push(`status:${code}`)
|
|
1592
|
+
},
|
|
1593
|
+
}
|
|
1594
|
+
const [req] = NewRequest(
|
|
1595
|
+
MethodGet,
|
|
1596
|
+
'http://example.invalid/streamed.mjs',
|
|
1597
|
+
null,
|
|
1598
|
+
)
|
|
1599
|
+
|
|
1600
|
+
await FileServer(root).ServeHTTP(writer, req)
|
|
1601
|
+
|
|
1602
|
+
expect(readSawHeader).toBe(true)
|
|
1603
|
+
expect(writes).toEqual(['status:200', 'streamed-body'])
|
|
1604
|
+
expect(Header_Get(header, 'Content-Type')).toBe('text/javascript; charset=utf-8')
|
|
1605
|
+
})
|
|
1606
|
+
|
|
1402
1607
|
it('awaits ServeContent writes before returning', async () => {
|
|
1403
1608
|
const writes: string[] = []
|
|
1404
1609
|
const writer: ResponseWriter = {
|
package/gs/net/http/index.ts
CHANGED
|
@@ -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
|
|
|
@@ -248,6 +250,8 @@ export function StatusText(code: number): string {
|
|
|
248
250
|
}
|
|
249
251
|
|
|
250
252
|
export type Header = Map<string, $.Slice<string>>
|
|
253
|
+
type HeaderBox = { __goValue: HeaderValue }
|
|
254
|
+
type HeaderValue = Header | $.VarRef<Header> | HeaderBox
|
|
251
255
|
|
|
252
256
|
export const Header = Map as {
|
|
253
257
|
new (entries?: Iterable<readonly [string, $.Slice<string>]> | null): Header
|
|
@@ -257,48 +261,62 @@ export function CanonicalHeaderKey(s: string): string {
|
|
|
257
261
|
return canonicalMIMEHeaderKey(s)
|
|
258
262
|
}
|
|
259
263
|
|
|
260
|
-
|
|
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)
|
|
261
279
|
key = canonicalMIMEHeaderKey(key)
|
|
262
|
-
const values = Array.from(
|
|
280
|
+
const values = Array.from(headers.get(key) ?? [])
|
|
263
281
|
values.push(value)
|
|
264
|
-
|
|
282
|
+
headers.set(key, $.arrayToSlice(values))
|
|
265
283
|
}
|
|
266
284
|
|
|
267
|
-
export function Header_Del(h:
|
|
268
|
-
h.delete(canonicalMIMEHeaderKey(key))
|
|
285
|
+
export function Header_Del(h: HeaderValue, key: string): void {
|
|
286
|
+
headerMap(h).delete(canonicalMIMEHeaderKey(key))
|
|
269
287
|
}
|
|
270
288
|
|
|
271
|
-
export function Header_Get(h:
|
|
272
|
-
const values = h.get(canonicalMIMEHeaderKey(key))
|
|
289
|
+
export function Header_Get(h: HeaderValue, key: string): string {
|
|
290
|
+
const values = headerMap(h).get(canonicalMIMEHeaderKey(key))
|
|
273
291
|
return values == null || values.length === 0 ? '' : String(values[0])
|
|
274
292
|
}
|
|
275
293
|
|
|
276
|
-
export function Header_Set(h:
|
|
277
|
-
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]))
|
|
278
296
|
}
|
|
279
297
|
|
|
280
|
-
export function Header_Values(h:
|
|
281
|
-
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
|
|
282
300
|
}
|
|
283
301
|
|
|
284
|
-
export function Header_Clone(h:
|
|
302
|
+
export function Header_Clone(h: HeaderValue): Header {
|
|
285
303
|
const cloned = new Header()
|
|
286
|
-
for (const [key, values] of h.entries()) {
|
|
304
|
+
for (const [key, values] of headerMap(h).entries()) {
|
|
287
305
|
cloned.set(key, $.arrayToSlice(Array.from(values ?? [])))
|
|
288
306
|
}
|
|
289
307
|
return cloned
|
|
290
308
|
}
|
|
291
309
|
|
|
292
|
-
export function Header_Write(h:
|
|
310
|
+
export function Header_Write(h: HeaderValue, w: io.Writer): $.GoError {
|
|
293
311
|
return Header_WriteSubset(h, w, null)
|
|
294
312
|
}
|
|
295
313
|
|
|
296
314
|
export function Header_WriteSubset(
|
|
297
|
-
h:
|
|
315
|
+
h: HeaderValue,
|
|
298
316
|
w: io.Writer,
|
|
299
317
|
exclude: Map<string, boolean> | null,
|
|
300
318
|
): $.GoError {
|
|
301
|
-
for (const [key, values] of h.entries()) {
|
|
319
|
+
for (const [key, values] of headerMap(h).entries()) {
|
|
302
320
|
if (exclude?.get(key) === true) {
|
|
303
321
|
continue
|
|
304
322
|
}
|
|
@@ -615,15 +633,15 @@ function asciiLower(value: string): [string, boolean] {
|
|
|
615
633
|
return [value.toLowerCase(), true]
|
|
616
634
|
}
|
|
617
635
|
|
|
618
|
-
export function SetCookie(
|
|
636
|
+
export async function SetCookie(
|
|
619
637
|
w: ResponseWriter | null,
|
|
620
638
|
cookie: Cookie | $.VarRef<Cookie> | null,
|
|
621
|
-
): void {
|
|
639
|
+
): Promise<void> {
|
|
622
640
|
const c = $.pointerValue<Cookie | null>(cookie)
|
|
623
641
|
if (w == null || c == null) {
|
|
624
642
|
return
|
|
625
643
|
}
|
|
626
|
-
Header_Add(w.Header(), 'Set-Cookie', c.String())
|
|
644
|
+
Header_Add(await w.Header(), 'Set-Cookie', c.String())
|
|
627
645
|
}
|
|
628
646
|
|
|
629
647
|
class memoryResponseWriter implements ResponseWriter {
|
|
@@ -694,9 +712,9 @@ function inProcessServerRequest(request: Request): Request {
|
|
|
694
712
|
}
|
|
695
713
|
|
|
696
714
|
export interface ResponseWriter {
|
|
697
|
-
Header(): Header
|
|
698
|
-
Write(p: $.Slice<number>): [number, $.GoError]
|
|
699
|
-
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>
|
|
700
718
|
}
|
|
701
719
|
|
|
702
720
|
export class Request {
|
|
@@ -1491,7 +1509,7 @@ async function readFetchBody(
|
|
|
1491
1509
|
type maybePromise<T> = T | Promise<T>
|
|
1492
1510
|
|
|
1493
1511
|
export interface FileSystem {
|
|
1494
|
-
Open(name: string): [File | null, $.GoError]
|
|
1512
|
+
Open(name: string): maybePromise<[File | null, $.GoError]>
|
|
1495
1513
|
}
|
|
1496
1514
|
|
|
1497
1515
|
export interface File extends io.Closer, io.Reader, io.Seeker {
|
|
@@ -1513,9 +1531,9 @@ interface fileServerFile {
|
|
|
1513
1531
|
|
|
1514
1532
|
export function FS(fsys: fs.FS): FileSystem {
|
|
1515
1533
|
return {
|
|
1516
|
-
Open(name: string): [File | null, $.GoError] {
|
|
1534
|
+
async Open(name: string): Promise<[File | null, $.GoError]> {
|
|
1517
1535
|
const cleaned = cleanFileServerPath(name)
|
|
1518
|
-
const [file, err] = fsys?.Open(cleaned) ?? [null, fs.ErrInvalid]
|
|
1536
|
+
const [file, err] = (await fsys?.Open(cleaned)) ?? [null, fs.ErrInvalid]
|
|
1519
1537
|
if (err != null || file == null) {
|
|
1520
1538
|
return [null, err]
|
|
1521
1539
|
}
|
|
@@ -1570,17 +1588,27 @@ export function FileServer(root: fileServerFileSystem | null): Handler {
|
|
|
1570
1588
|
NotFound(w, req)
|
|
1571
1589
|
return
|
|
1572
1590
|
}
|
|
1573
|
-
const
|
|
1574
|
-
if (
|
|
1575
|
-
|
|
1576
|
-
|
|
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
|
+
}
|
|
1577
1599
|
}
|
|
1578
1600
|
if (info?.Size != null) {
|
|
1579
|
-
Header_Set(
|
|
1601
|
+
Header_Set(header, 'Content-Length', String(info.Size()))
|
|
1580
1602
|
}
|
|
1581
|
-
w.WriteHeader(StatusOK)
|
|
1603
|
+
await w.WriteHeader(StatusOK)
|
|
1582
1604
|
if (req.Method !== MethodHead) {
|
|
1583
|
-
|
|
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
|
+
}
|
|
1584
1612
|
}
|
|
1585
1613
|
} finally {
|
|
1586
1614
|
await file.Close()
|
|
@@ -2131,7 +2159,7 @@ export function NotFoundHandler(): Handler {
|
|
|
2131
2159
|
export function RedirectHandler(url: string, code: number): Handler {
|
|
2132
2160
|
return {
|
|
2133
2161
|
ServeHTTP(w, r) {
|
|
2134
|
-
Redirect(w, r, url, code)
|
|
2162
|
+
return Redirect(w, r, url, code)
|
|
2135
2163
|
},
|
|
2136
2164
|
}
|
|
2137
2165
|
}
|
|
@@ -2168,17 +2196,20 @@ export function NotFound(
|
|
|
2168
2196
|
Error(w, '404 page not found', StatusNotFound)
|
|
2169
2197
|
}
|
|
2170
2198
|
|
|
2171
|
-
export function Redirect(
|
|
2199
|
+
export async function Redirect(
|
|
2172
2200
|
w: ResponseWriter | null,
|
|
2173
2201
|
_r: Request | $.VarRef<Request> | null,
|
|
2174
2202
|
url: string,
|
|
2175
2203
|
code: number,
|
|
2176
|
-
): void {
|
|
2177
|
-
|
|
2204
|
+
): Promise<void> {
|
|
2205
|
+
if (w == null) {
|
|
2206
|
+
return
|
|
2207
|
+
}
|
|
2208
|
+
const header = await w.Header()
|
|
2178
2209
|
if (header != null) {
|
|
2179
2210
|
Header_Set(header, 'Location', url)
|
|
2180
2211
|
}
|
|
2181
|
-
w
|
|
2212
|
+
await w.WriteHeader(code)
|
|
2182
2213
|
}
|
|
2183
2214
|
|
|
2184
2215
|
export function ParseTime(text: string): [time.Time, $.GoError] {
|
package/gs/net/http/meta.json
CHANGED
|
@@ -27,19 +27,19 @@ class writer implements ResponseWriter {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
describe('net/http/pprof override', () => {
|
|
30
|
-
it('serves the index text expected by debug handlers', () => {
|
|
30
|
+
it('serves the index text expected by debug handlers', async () => {
|
|
31
31
|
const w = new writer()
|
|
32
32
|
|
|
33
|
-
Index(w, null)
|
|
33
|
+
await Index(w, null)
|
|
34
34
|
|
|
35
35
|
expect(Header_Get(w.Header(), 'Content-Type')).toContain('text/html')
|
|
36
36
|
expect(w.String()).toContain('full goroutine stack dump')
|
|
37
37
|
})
|
|
38
38
|
|
|
39
|
-
it('serves named runtime profiles', () => {
|
|
39
|
+
it('serves named runtime profiles', async () => {
|
|
40
40
|
const w = new writer()
|
|
41
41
|
|
|
42
|
-
Handler('goroutine').ServeHTTP(w, null)
|
|
42
|
+
await Handler('goroutine').ServeHTTP(w, null)
|
|
43
43
|
|
|
44
44
|
expect(Header_Get(w.Header(), 'Content-Type')).toContain('text/plain')
|
|
45
45
|
expect(w.String()).toContain('goroutine profile')
|
|
@@ -2,82 +2,85 @@ import * as $ from '@goscript/builtin/index.js'
|
|
|
2
2
|
import * as http from '@goscript/net/http/index.js'
|
|
3
3
|
import * as pprof from '@goscript/runtime/pprof/index.js'
|
|
4
4
|
|
|
5
|
-
function writeString(
|
|
6
|
-
w
|
|
5
|
+
async function writeString(
|
|
6
|
+
w: http.ResponseWriter | null,
|
|
7
|
+
value: string,
|
|
8
|
+
): Promise<void> {
|
|
9
|
+
await w?.Write($.stringToBytes(value))
|
|
7
10
|
}
|
|
8
11
|
|
|
9
|
-
export function Index(
|
|
12
|
+
export async function Index(
|
|
10
13
|
w: http.ResponseWriter | null,
|
|
11
14
|
_r: http.Request | $.VarRef<http.Request> | null,
|
|
12
|
-
): void {
|
|
13
|
-
const header = w?.Header()
|
|
15
|
+
): Promise<void> {
|
|
16
|
+
const header = await w?.Header()
|
|
14
17
|
if (header != null) {
|
|
15
18
|
http.Header_Set(header, 'Content-Type', 'text/html; charset=utf-8')
|
|
16
19
|
}
|
|
17
|
-
writeString(
|
|
20
|
+
await writeString(
|
|
18
21
|
w,
|
|
19
22
|
'<html><body><a href="goroutine?debug=2">full goroutine stack dump</a></body></html>',
|
|
20
23
|
)
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
export function Cmdline(
|
|
26
|
+
export async function Cmdline(
|
|
24
27
|
w: http.ResponseWriter | null,
|
|
25
28
|
_r: http.Request | $.VarRef<http.Request> | null,
|
|
26
|
-
): void {
|
|
27
|
-
const header = w?.Header()
|
|
29
|
+
): Promise<void> {
|
|
30
|
+
const header = await w?.Header()
|
|
28
31
|
if (header != null) {
|
|
29
32
|
http.Header_Set(header, 'Content-Type', 'text/plain; charset=utf-8')
|
|
30
33
|
}
|
|
31
|
-
writeString(w, 'goscript')
|
|
34
|
+
await writeString(w, 'goscript')
|
|
32
35
|
}
|
|
33
36
|
|
|
34
|
-
export function Profile(
|
|
37
|
+
export async function Profile(
|
|
35
38
|
w: http.ResponseWriter | null,
|
|
36
39
|
_r: http.Request | $.VarRef<http.Request> | null,
|
|
37
|
-
): void {
|
|
38
|
-
const header = w?.Header()
|
|
40
|
+
): Promise<void> {
|
|
41
|
+
const header = await w?.Header()
|
|
39
42
|
if (header != null) {
|
|
40
43
|
http.Header_Set(header, 'Content-Type', 'application/octet-stream')
|
|
41
44
|
}
|
|
42
|
-
writeString(w, 'cpu profile\n')
|
|
45
|
+
await writeString(w, 'cpu profile\n')
|
|
43
46
|
}
|
|
44
47
|
|
|
45
|
-
export function Symbol(
|
|
48
|
+
export async function Symbol(
|
|
46
49
|
w: http.ResponseWriter | null,
|
|
47
50
|
_r: http.Request | $.VarRef<http.Request> | null,
|
|
48
|
-
): void {
|
|
49
|
-
const header = w?.Header()
|
|
51
|
+
): Promise<void> {
|
|
52
|
+
const header = await w?.Header()
|
|
50
53
|
if (header != null) {
|
|
51
54
|
http.Header_Set(header, 'Content-Type', 'text/plain; charset=utf-8')
|
|
52
55
|
}
|
|
53
|
-
writeString(w, 'num_symbols: 0\n')
|
|
56
|
+
await writeString(w, 'num_symbols: 0\n')
|
|
54
57
|
}
|
|
55
58
|
|
|
56
|
-
export function Trace(
|
|
59
|
+
export async function Trace(
|
|
57
60
|
w: http.ResponseWriter | null,
|
|
58
61
|
_r: http.Request | $.VarRef<http.Request> | null,
|
|
59
|
-
): void {
|
|
60
|
-
const header = w?.Header()
|
|
62
|
+
): Promise<void> {
|
|
63
|
+
const header = await w?.Header()
|
|
61
64
|
if (header != null) {
|
|
62
65
|
http.Header_Set(header, 'Content-Type', 'application/octet-stream')
|
|
63
66
|
}
|
|
64
|
-
writeString(w, 'trace\n')
|
|
67
|
+
await writeString(w, 'trace\n')
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
export function Handler(name: string): http.Handler {
|
|
68
71
|
return {
|
|
69
|
-
ServeHTTP(w) {
|
|
70
|
-
const header = w?.Header()
|
|
72
|
+
async ServeHTTP(w) {
|
|
73
|
+
const header = await w?.Header()
|
|
71
74
|
if (header != null) {
|
|
72
75
|
http.Header_Set(header, 'Content-Type', 'text/plain; charset=utf-8')
|
|
73
76
|
}
|
|
74
77
|
const profile = pprof.Lookup(name)
|
|
75
78
|
if (profile == null) {
|
|
76
|
-
http.NotFound(w, null)
|
|
79
|
+
await http.NotFound(w, null)
|
|
77
80
|
return
|
|
78
81
|
}
|
|
79
|
-
writeString(w, `${name} profile\n`)
|
|
80
|
-
profile.WriteTo(w, 1)
|
|
82
|
+
await writeString(w, `${name} profile\n`)
|
|
83
|
+
profile.WriteTo(w as unknown as any, 1)
|
|
81
84
|
},
|
|
82
85
|
}
|
|
83
86
|
}
|