@amritk/nish-x86_64-linux 0.21.0 → 0.23.0

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/std/json.ts CHANGED
@@ -1220,8 +1220,9 @@ export class JsonWriter {
1220
1220
  * `bytes[off .. off + len)`, copied as it is and counted as one value: a
1221
1221
  * fragment the caller encoded already, such as a cached object. Nothing in
1222
1222
  * it is checked, so a fragment that is not one whole JSON value makes a
1223
- * text that is not JSON either. The bytes are copied front to back, so
1224
- * `bytes` is not the writer's own buffer at a window the copy overtakes.
1223
+ * text that is not JSON either. The bytes are copied in one move
1224
+ * (`copyBytes`), as if all were read before any was written, so a window
1225
+ * of the writer's own buffer comes out as it was.
1225
1226
  * In an indented text the fragment goes in where a value would, after its
1226
1227
  * line break and indent, but its own bytes are not re-indented: an object
1227
1228
  * fragment stays on one line, where `JSON.stringify` would spread it out.
@@ -1243,11 +1244,7 @@ export class JsonWriter {
1243
1244
  this.failed = JSON_WRITE_FULL
1244
1245
  return
1245
1246
  }
1246
- for (let k: i32 = 0; k < len && off + k < toI32(bytes.length) && at + k < toI32(buf.length); k += 1) {
1247
- if (off + k >= 0 && at + k >= 0) {
1248
- buf[at + k] = bytes[off + k]
1249
- }
1250
- }
1247
+ copyBytes(buf, at, bytes, off, len)
1251
1248
  this.end = at + len
1252
1249
  jsonWriterValueDone(this)
1253
1250
  }
@@ -113,6 +113,7 @@
113
113
  * implementation.
114
114
  */
115
115
  import { Secret } from "nish:secret"
116
+ import { copyBytes } from "nish/bytes"
116
117
  import { netClose, netRead, netWrite, tcpAccept } from "nish:net"
117
118
  import {
118
119
  HTTP1_BODY,
@@ -120,6 +121,8 @@ import {
120
121
  HTTP1_CLOSED,
121
122
  HTTP1_END,
122
123
  HTTP1_ERROR,
124
+ HTTP1_FIRST_HEAD,
125
+ HTTP1_FIRST_INPUT,
123
126
  HTTP1_HEAD,
124
127
  HTTP1_NO_BODY,
125
128
  HTTP1_REFUSED,
@@ -240,6 +243,11 @@ const http1ServerCheckWindow = (what: string, buf: u8[], off: i32, len: i32): vo
240
243
 
241
244
  /** Copies `src[off .. off + n)` to `out` at `at`, which the caller has made sure holds it, and answers where it ends. */
242
245
  const http1ServerCopy = (out: u8[], at: i32, src: u8[], off: i32, n: i32): i32 => {
246
+ if (n > 0 && at >= 0 && off >= 0 && at <= toI32(out.length) - n && off <= toI32(src.length) - n) {
247
+ // Both windows fit, as they do for every caller: one move, not a byte loop.
248
+ copyBytes(out, at, src, off, n)
249
+ return at + n
250
+ }
243
251
  for (let k: i32 = 0; k < n && at + k < toI32(out.length) && off + k < toI32(src.length); k++) {
244
252
  if (at + k >= 0 && off + k >= 0) {
245
253
  out[at + k] = src[off + k]
@@ -352,6 +360,15 @@ export class Http1Config {
352
360
  * of its head each `idleTimeout` would hold its slot for good (slowloris).
353
361
  */
354
362
  headTimeout: i32 = 30000
363
+ /**
364
+ * Lend a slot's input and head buffers back to the server between
365
+ * exchanges: once a response is sent and no byte of the next request has
366
+ * come, the slot gives both to pools the server's slots share, and takes
367
+ * them again with the next byte. The memory they hold then follows the
368
+ * connections mid-request rather than every keep-alive connection open,
369
+ * for two pool operations a request. Off by default.
370
+ */
371
+ idleInput: boolean = false
355
372
  }
356
373
 
357
374
  /** The reason phrase a status is sent with; RFC 9112 §4 lets a client ignore it. */
@@ -470,8 +487,12 @@ export class Http1Connection {
470
487
  date: HttpDate
471
488
  /** The request parser: its head (`methodIs`, `targetIs`, `header`, spans) is the last `H1_REQUEST`'s. */
472
489
  parser: Http1Parser
473
- /** The frame decoder, once the slot has switched to WebSocket. */
474
- ws: WsDecoder
490
+ /**
491
+ * The frame decoder: made when the slot first switches to WebSocket, so a
492
+ * slot that only ever speaks HTTP never holds its buffers, then kept for
493
+ * the slot's later peers.
494
+ */
495
+ ws: WsDecoder | null
475
496
  /**
476
497
  * Bytes to send are `output[outputStart .. outputEnd)`. Empty until the
477
498
  * connection first needs room to write: then its own buffer, made once, or
@@ -482,9 +503,12 @@ export class Http1Connection {
482
503
  noOutput: u8[]
483
504
  /** Where `output` is borrowed from, for a connection of a pool; null for one that keeps its own. */
484
505
  pool: Http1OutputPool | null
506
+ /** Where the parser's input and head buffers go while the slot rests, with `Http1Config.idleInput`; null otherwise. */
507
+ inputs: Http1OutputPool | null
508
+ heads: Http1OutputPool | null
485
509
  /** `H1_BODY`'s and `H1_WS_MESSAGE`'s bytes. */
486
510
  data: u8[]
487
- /** A ping's payload, kept until its pong fits. */
511
+ /** A ping's payload, kept until its pong fits: empty until the first ping. */
488
512
  pong: u8[]
489
513
  /** No fields, for the responses the connection writes itself: made once. */
490
514
  noFields: u8[][]
@@ -561,12 +585,14 @@ export class Http1Connection {
561
585
  this.parser = new Http1Parser(config.maxTarget, config.maxHeaderBytes, config.maxHeaders, config.maxBody)
562
586
  this.parser.keepText = false
563
587
  this.parser.maxChunk = config.chunkSize
564
- this.ws = new WsDecoder(true, config.maxMessage)
588
+ this.ws = null
565
589
  this.noOutput = []
566
590
  this.output = this.noOutput
567
591
  this.pool = null
592
+ this.inputs = null
593
+ this.heads = null
568
594
  this.data = this.noOutput
569
- this.pong = new Array<u8>(WS_MAX_CONTROL)
595
+ this.pong = this.noOutput
570
596
  this.noFields = []
571
597
  // A line must fit whole before the parser can read it, so the buffer
572
598
  // holds the longest request line or field line however small a chunk is.
@@ -584,7 +610,10 @@ export class Http1Connection {
584
610
  /** Puts the connection back as the constructor left it, for the next peer. It allocates nothing. */
585
611
  restart(): void {
586
612
  this.parser.restart()
587
- this.ws.reset()
613
+ const ws: WsDecoder | null = this.ws
614
+ if (ws !== null) {
615
+ ws.reset()
616
+ }
588
617
  this.outputStart = 0
589
618
  this.outputEnd = 0
590
619
  this.data = this.noOutput
@@ -608,6 +637,49 @@ export class Http1Connection {
608
637
  this.headTimed = false
609
638
  this.headSince = 0
610
639
  this.release()
640
+ this.park()
641
+ }
642
+
643
+ /**
644
+ * Gives the parser's input and head buffers to `inputs` and `heads` while
645
+ * the connection rests between exchanges: speaking HTTP, no request open,
646
+ * nothing fed and unread. Does nothing without the pools or otherwise.
647
+ */
648
+ park(): void {
649
+ const inputs: Http1OutputPool | null = this.inputs
650
+ const heads: Http1OutputPool | null = this.heads
651
+ const p: Http1Parser = this.parser
652
+ if (inputs === null || heads === null || this.state !== H1_S_HTTP || this.requestOpen || !p.resting()) {
653
+ return
654
+ }
655
+ if (toI32(p.buf.length) > 0) {
656
+ inputs.give(p.buf)
657
+ p.buf = this.noOutput
658
+ p.body = this.noOutput
659
+ p.start = 0
660
+ p.end = 0
661
+ p.scan = 0
662
+ p.lineStart = 0
663
+ p.lineEnd = 0
664
+ }
665
+ if (toI32(p.head.length) > 0) {
666
+ heads.give(p.head)
667
+ p.head = this.noOutput
668
+ }
669
+ }
670
+
671
+ /** Takes back the buffers `park` gave away, before bytes are fed. */
672
+ unpark(): void {
673
+ const inputs: Http1OutputPool | null = this.inputs
674
+ const heads: Http1OutputPool | null = this.heads
675
+ const p: Http1Parser = this.parser
676
+ if (inputs !== null && toI32(p.buf.length) === 0) {
677
+ p.buf = inputs.take()
678
+ p.body = p.buf
679
+ }
680
+ if (heads !== null && toI32(p.head.length) === 0) {
681
+ p.head = heads.take()
682
+ }
611
683
  }
612
684
 
613
685
  // ---- Bytes in and out ---------------------------------------------------------
@@ -622,7 +694,9 @@ export class Http1Connection {
622
694
  if (this.writeHeld || this.inputEnded || this.state === H1_S_CLOSING || this.state === H1_S_FAILED) {
623
695
  return 0
624
696
  }
625
- const held: i32 = this.state === H1_S_WEBSOCKET ? this.ws.end - this.ws.start : this.parser.buffered()
697
+ const ws: WsDecoder | null = this.ws
698
+ const held: i32 =
699
+ this.state === H1_S_WEBSOCKET && ws !== null ? ws.end - ws.start : this.parser.buffered()
626
700
  const room: i32 = this.inputLimit() - held
627
701
  return room > 0 ? room : 0
628
702
  }
@@ -640,8 +714,9 @@ export class Http1Connection {
640
714
  return 0
641
715
  }
642
716
  if (this.state === H1_S_WEBSOCKET) {
643
- this.ws.feed(buf, off, n)
717
+ this.decoder().feed(buf, off, n)
644
718
  } else {
719
+ this.unpark()
645
720
  this.parser.feed(buf, off, n)
646
721
  }
647
722
  return n
@@ -727,12 +802,8 @@ export class Http1Connection {
727
802
  const output: u8[] = this.output
728
803
  const live: i32 = this.outputEnd - this.outputStart
729
804
  if (this.outputStart > 0) {
730
- for (
731
- let k: i32 = 0;
732
- k < live && k < toI32(output.length) && this.outputStart + k < toI32(output.length);
733
- k++
734
- ) {
735
- output[k] = output[this.outputStart + k]
805
+ if (live > 0 && this.outputStart <= toI32(output.length) - live) {
806
+ copyBytes(output, 0, output, this.outputStart, live)
736
807
  }
737
808
  this.outputStart = 0
738
809
  this.outputEnd = live
@@ -784,8 +855,9 @@ export class Http1Connection {
784
855
  this.response = H1_R_NONE
785
856
  if (this.upgraded) {
786
857
  const p: Http1Parser = this.parser
787
- this.ws.reset()
788
- this.ws.feed(p.buf, p.start, p.buffered())
858
+ const ws: WsDecoder = this.decoder()
859
+ ws.reset()
860
+ ws.feed(p.buf, p.start, p.buffered())
789
861
  this.state = H1_S_WEBSOCKET
790
862
  return this.nextFrame()
791
863
  }
@@ -867,35 +939,50 @@ export class Http1Connection {
867
939
  return H1_ERROR
868
940
  }
869
941
 
942
+ /** The frame decoder, made on the slot's first switch to WebSocket. */
943
+ decoder(): WsDecoder {
944
+ const ws: WsDecoder | null = this.ws
945
+ if (ws !== null) {
946
+ return ws
947
+ }
948
+ const made: WsDecoder = new WsDecoder(true, this.config.maxMessage)
949
+ this.ws = made
950
+ return made
951
+ }
952
+
870
953
  /** `next` once the slot has switched to WebSocket. */
871
954
  nextFrame(): i32 {
872
955
  while (true) {
873
956
  if (this.pongPending && !this.sendPong()) {
874
957
  return H1_NEED_MORE
875
958
  }
876
- const event: i32 = this.ws.next()
959
+ const ws: WsDecoder = this.decoder()
960
+ const event: i32 = ws.next()
877
961
  if (event === WS_MESSAGE) {
878
- this.opcode = this.ws.opcode
879
- this.data = this.ws.data
962
+ this.opcode = ws.opcode
963
+ this.data = ws.data
880
964
  this.dataStart = 0
881
- this.dataLength = this.ws.dataLen
965
+ this.dataLength = ws.dataLen
882
966
  return H1_WS_MESSAGE
883
967
  }
884
968
  if (event === WS_PING) {
885
- const n: i32 = this.ws.dataLen
886
- for (let k: i32 = 0; k < n && k < toI32(this.pong.length) && k < toI32(this.ws.data.length); k++) {
887
- this.pong[k] = this.ws.data[k]
969
+ const n: i32 = ws.dataLen
970
+ if (this.pong.length === 0) {
971
+ this.pong = new Array<u8>(WS_MAX_CONTROL)
972
+ }
973
+ for (let k: i32 = 0; k < n && k < toI32(this.pong.length) && k < toI32(ws.data.length); k++) {
974
+ this.pong[k] = ws.data[k]
888
975
  }
889
976
  this.pongLength = n
890
977
  this.pongPending = true
891
978
  } else if (event === WS_CLOSE) {
892
- this.status = this.ws.closeCode
893
- this.sendClose(this.ws.closeCode)
979
+ this.status = ws.closeCode
980
+ this.sendClose(ws.closeCode)
894
981
  this.state = H1_S_CLOSING
895
982
  return H1_WS_CLOSE
896
983
  } else if (event === WS_ERROR) {
897
- this.status = this.ws.closeCode
898
- this.sendClose(this.ws.closeCode)
984
+ this.status = ws.closeCode
985
+ this.sendClose(ws.closeCode)
899
986
  this.state = H1_S_FAILED
900
987
  return H1_ERROR
901
988
  } else if (event === WS_NEED_MORE) {
@@ -935,7 +1022,7 @@ export class Http1Connection {
935
1022
  const payload: u8[] | null = websocketClosePayload(code, "")
936
1023
  // A code that may not be sent (1005, for a close that had none) is
937
1024
  // answered with an empty close.
938
- const body: u8[] = payload === null ? this.pong : payload
1025
+ const body: u8[] = payload === null ? this.noOutput : payload
939
1026
  const n: i32 = payload === null ? H1_ZERO : toI32(body.length)
940
1027
  const end: i32 = websocketWriteFrame(
941
1028
  this.output,
@@ -1245,6 +1332,30 @@ export class Http1Connection {
1245
1332
  expired(now: i64): boolean {
1246
1333
  return this.idle(now) || this.headLate(now)
1247
1334
  }
1335
+
1336
+ /**
1337
+ * The soonest `expired` can turn true, however the connection moves from
1338
+ * here: `lastActive` only grows, and a head clock not yet started starts
1339
+ * no earlier than it.
1340
+ */
1341
+ expiresNoSooner(): i64 {
1342
+ const idle: i64 = toI64(this.config.idleTimeout) * toI64(1000000)
1343
+ const head: i64 = toI64(this.config.headTimeout) * toI64(1000000)
1344
+ const soonest: i64 = this.lastActive + (head < idle ? head : idle)
1345
+ if (this.headTimed && this.headSince + head < soonest) {
1346
+ return this.headSince + head
1347
+ }
1348
+ return soonest
1349
+ }
1350
+ }
1351
+
1352
+ /**
1353
+ * The soonest any slot accepted after now can expire: no sooner than the
1354
+ * shorter of `idleTimeout` and `headTimeout` from now.
1355
+ */
1356
+ const http1ExpireFloor = (config: Http1Config): i64 => {
1357
+ const shorter: i32 = config.headTimeout < config.idleTimeout ? config.headTimeout : config.idleTimeout
1358
+ return monotonicNanos() + toI64(shorter) * toI64(1000000)
1248
1359
  }
1249
1360
 
1250
1361
  // ---- Carriers -------------------------------------------------------------------
@@ -1273,6 +1384,9 @@ export class Http1Server {
1273
1384
  armed: i32[]
1274
1385
  /** The output buffers the slots borrow while they write. */
1275
1386
  outputs: Http1OutputPool
1387
+ /** The input and head buffers resting slots lend, with `Http1Config.idleInput`. */
1388
+ inputs: Http1OutputPool
1389
+ heads: Http1OutputPool
1276
1390
  /**
1277
1391
  * The slots closed since they were used, a stack whose top `accept` takes
1278
1392
  * before it builds a slot past the high-water mark: finding one costs the
@@ -1288,6 +1402,14 @@ export class Http1Server {
1288
1402
  slots: i32 = 1
1289
1403
  /** The listening socket, which the program made with `tcpListen` and still owns. */
1290
1404
  listener: i32 = -1
1405
+ /**
1406
+ * No slot can expire before this (`monotonicNanos`), so `expire` called
1407
+ * sooner walks none: it walks every slot only about once per the shorter
1408
+ * timeout, however many there are.
1409
+ */
1410
+ expireAfter: i64 = 0
1411
+ /** How many calls of `expire` walked every slot, for a program measuring its cost. */
1412
+ expireScans: i32 = 0
1291
1413
 
1292
1414
  /**
1293
1415
  * A pool of `size` slots, at least one, serving `config` on `listener`.
@@ -1305,6 +1427,8 @@ export class Http1Server {
1305
1427
  this.free = []
1306
1428
  this.slots = size < 1 ? 1 : size
1307
1429
  this.outputs = new Http1OutputPool(config.outputSize)
1430
+ this.inputs = new Http1OutputPool(HTTP1_FIRST_INPUT)
1431
+ this.heads = new Http1OutputPool(HTTP1_FIRST_HEAD)
1308
1432
  this.date = new HttpDate()
1309
1433
  this.scratch = new Array<u8>(config.chunkSize)
1310
1434
  this.peer = new Array<u8>(H1_ADDRESS_SIZE)
@@ -1315,6 +1439,10 @@ export class Http1Server {
1315
1439
  const conn: Http1Connection = new Http1Connection(this.config)
1316
1440
  conn.pool = this.outputs
1317
1441
  conn.date = this.date
1442
+ if (this.config.idleInput) {
1443
+ conn.inputs = this.inputs
1444
+ conn.heads = this.heads
1445
+ }
1318
1446
  this.connections.push(conn)
1319
1447
  return conn
1320
1448
  }
@@ -1473,6 +1601,7 @@ export class Http1Server {
1473
1601
  if (conn.isDone()) {
1474
1602
  return H1_DONE
1475
1603
  }
1604
+ conn.park()
1476
1605
  let wants: i32 = 0
1477
1606
  if (conn.inputRoom() > 0) {
1478
1607
  wants = wants | H1_WANT_READ
@@ -1538,13 +1667,24 @@ export class Http1Server {
1538
1667
  * head for longer than `headTimeout`, at `now` (`monotonicNanos`); answers how many.
1539
1668
  */
1540
1669
  expire(now: i64): i32 {
1670
+ if (now < this.expireAfter) {
1671
+ return 0
1672
+ }
1673
+ this.expireScans = this.expireScans + 1
1674
+ let soonest: i64 = http1ExpireFloor(this.config)
1541
1675
  let n: i32 = 0
1542
1676
  for (let k: i32 = 0; k < toI32(this.fds.length) && k < toI32(this.connections.length); k++) {
1543
- if (this.fds[k] >= 0 && this.connections[k].expired(now)) {
1544
- this.close(k)
1545
- n = n + 1
1677
+ if (this.fds[k] >= 0) {
1678
+ const conn = this.connections[k]
1679
+ if (conn.expired(now)) {
1680
+ this.close(k)
1681
+ n = n + 1
1682
+ } else if (conn.expiresNoSooner() < soonest) {
1683
+ soonest = conn.expiresNoSooner()
1684
+ }
1546
1685
  }
1547
1686
  }
1687
+ this.expireAfter = soonest
1548
1688
  return n
1549
1689
  }
1550
1690
  }
@@ -1580,6 +1720,13 @@ export class Http1TlsServer {
1580
1720
  scratch: u8[]
1581
1721
  /** The output buffers the slots borrow while they write. */
1582
1722
  outputs: Http1OutputPool
1723
+ /** The input and head buffers resting slots lend, with `Http1Config.idleInput`. */
1724
+ inputs: Http1OutputPool
1725
+ heads: Http1OutputPool
1726
+ /** No slot can expire before this (`monotonicNanos`): `Http1Server.expireAfter`. */
1727
+ expireAfter: i64 = 0
1728
+ /** How many calls of `expire` walked every slot. */
1729
+ expireScans: i32 = 0
1583
1730
 
1584
1731
  /**
1585
1732
  * A pool of `size` slots, at least one, serving `config` over TLS under
@@ -1593,6 +1740,8 @@ export class Http1TlsServer {
1593
1740
  this.tls = new TlsTcpServer(tlsConfig, listener, size)
1594
1741
  this.connections = []
1595
1742
  this.outputs = new Http1OutputPool(config.outputSize)
1743
+ this.inputs = new Http1OutputPool(HTTP1_FIRST_INPUT)
1744
+ this.heads = new Http1OutputPool(HTTP1_FIRST_HEAD)
1596
1745
  this.date = new HttpDate()
1597
1746
  this.scratch = new Array<u8>(config.chunkSize)
1598
1747
  }
@@ -1613,6 +1762,10 @@ export class Http1TlsServer {
1613
1762
  const conn: Http1Connection = new Http1Connection(this.config)
1614
1763
  conn.pool = this.outputs
1615
1764
  conn.date = this.date
1765
+ if (this.config.idleInput) {
1766
+ conn.inputs = this.inputs
1767
+ conn.heads = this.heads
1768
+ }
1616
1769
  return conn
1617
1770
  }
1618
1771
 
@@ -1776,6 +1929,7 @@ export class Http1TlsServer {
1776
1929
  if (conn.isDone()) {
1777
1930
  return this.tls.shutdown(slot)
1778
1931
  }
1932
+ conn.park()
1779
1933
  const wants: i32 = this.tls.interest(slot)
1780
1934
  return conn.wantsWrite() && (wants & TLS_RECORD_DONE) === 0 ? wants | TLS_RECORD_WANT_WRITE : wants
1781
1935
  }
@@ -1816,13 +1970,24 @@ export class Http1TlsServer {
1816
1970
  * head for longer than `headTimeout`, at `now` (`monotonicNanos`); answers how many.
1817
1971
  */
1818
1972
  expire(now: i64): i32 {
1973
+ if (now < this.expireAfter) {
1974
+ return 0
1975
+ }
1976
+ this.expireScans = this.expireScans + 1
1977
+ let soonest: i64 = http1ExpireFloor(this.config)
1819
1978
  let n: i32 = 0
1820
1979
  for (let k: i32 = 0; k < this.tls.size() && k < toI32(this.connections.length); k++) {
1821
- if (this.tls.holds(k) && this.connections[k].expired(now)) {
1822
- this.tls.close(k)
1823
- n = n + 1
1980
+ if (this.tls.holds(k)) {
1981
+ const conn = this.connections[k]
1982
+ if (conn.expired(now)) {
1983
+ this.tls.close(k)
1984
+ n = n + 1
1985
+ } else if (conn.expiresNoSooner() < soonest) {
1986
+ soonest = conn.expiresNoSooner()
1987
+ }
1824
1988
  }
1825
1989
  }
1990
+ this.expireAfter = soonest
1826
1991
  return n
1827
1992
  }
1828
1993
  }
package/std/net/http1.ts CHANGED
@@ -80,6 +80,7 @@
80
80
  *
81
81
  * Written from RFC 9112 and RFC 9110, not ported from another implementation.
82
82
  */
83
+ import { copyBytes } from "nish/bytes"
83
84
 
84
85
  /** `next()`: the input so far holds no further event; feed more. */
85
86
  export const HTTP1_NEED_MORE: i32 = 0
@@ -234,11 +235,15 @@ export class Http1Parser {
234
235
  this.maxHeaderBytes = http1Clamp(maxHeaderBytes)
235
236
  this.maxHeaders = http1Clamp(maxHeaders)
236
237
  this.maxBody = http1Clamp(maxBody)
237
- this.buf = new Array<u8>(1024)
238
+ // The input and the head are made by the first byte fed and the first
239
+ // head kept, at HTTP1_FIRST_INPUT and HTTP1_FIRST_HEAD bytes: a parser
240
+ // that is built and never fed (a pool's spare slot) costs neither, and a
241
+ // caller that lends them out between requests puts empty ones here.
242
+ this.buf = []
238
243
  this.body = this.buf
239
244
  this.names = []
240
245
  this.values = []
241
- this.head = new Array<u8>(256)
246
+ this.head = []
242
247
  this.spans = new Array<i32>(64)
243
248
  this.tally = new Http1ListTally()
244
249
  this.restart()
@@ -261,19 +266,9 @@ export class Http1Parser {
261
266
  http1Reserve(this, len)
262
267
  const buf: u8[] = this.buf
263
268
  const at: i32 = this.end
264
- // A whole array is one `set`, a memmove; a window is copied byte by byte,
265
- // because `set` takes no window of its source.
266
- if (off === 0 && len === size) {
267
- buf.set(data, at)
268
- this.end = at + len
269
- return
270
- }
271
- for (let k: i32 = 0; k < len; k += 1) {
272
- const to: i32 = at + k
273
- if (to >= 0 && to < toI32(buf.length)) {
274
- buf[to] = data[off + k]
275
- }
276
- }
269
+ // One move: http1Reserve made room for `len` bytes at `at`, and the
270
+ // window was checked above.
271
+ copyBytes(buf, at, data, off, len)
277
272
  this.end = at + len
278
273
  }
279
274
 
@@ -359,6 +354,16 @@ export class Http1Parser {
359
354
  return http1HeadIs(this.head, spans[4 * found + 2], spans[4 * found + 3], protocol, true)
360
355
  }
361
356
 
357
+ /**
358
+ * Whether the parser waits for a request line with nothing fed and unread,
359
+ * so its input and head buffers hold nothing it needs: a caller may lend
360
+ * them out and leave empty arrays in `buf`, `body` and `head`, which the
361
+ * next feed and head make or the caller puts back first.
362
+ */
363
+ resting(): boolean {
364
+ return this.state === HTTP1_S_REQUEST_LINE && this.end === this.start
365
+ }
366
+
362
367
  /** How many bytes were fed and not consumed yet: what a caller bounds its reads by. */
363
368
  buffered(): i32 {
364
369
  return this.end - this.start
@@ -410,6 +415,11 @@ export class Http1Parser {
410
415
  }
411
416
  }
412
417
 
418
+ /** The input buffer a parser makes on its first feed, the smallest it grows from. */
419
+ export const HTTP1_FIRST_INPUT: i32 = 1024
420
+ /** The head buffer a parser makes for its first head. */
421
+ export const HTTP1_FIRST_HEAD: i32 = 256
422
+
413
423
  /** Makes room for `len` more bytes at `p.end`, moving the live bytes down first. */
414
424
  const http1Reserve = (p: Http1Parser, len: i32): void => {
415
425
  const old: u8[] = p.buf
@@ -418,7 +428,7 @@ const http1Reserve = (p: Http1Parser, len: i32): void => {
418
428
  return
419
429
  }
420
430
  const live: i32 = p.end - p.start
421
- let size: i32 = capacity
431
+ let size: i32 = capacity < HTTP1_FIRST_INPUT ? HTTP1_FIRST_INPUT : capacity
422
432
  while (size - live < len) {
423
433
  if (size > 1073741823) {
424
434
  panic("Http1Parser: more than 2^31 - 1 bytes buffered")
@@ -429,8 +439,9 @@ const http1Reserve = (p: Http1Parser, len: i32): void => {
429
439
  // the live bytes move to the front, and every index into them moves too.
430
440
  const buf: u8[] = size === capacity ? old : new Array<u8>(size)
431
441
  const from: i32 = p.start
432
- for (let k: i32 = 0; k < live && k < toI32(buf.length); k += 1) {
433
- buf[k] = old[from + k]
442
+ if (live > 0) {
443
+ // One move, overlapping when the buffer is the same one.
444
+ copyBytes(buf, 0, old, from, live)
434
445
  }
435
446
  p.buf = buf
436
447
  p.lineStart -= from
@@ -601,6 +612,9 @@ const http1HeadAppend = (p: Http1Parser, from: i32, to: i32): i32 => {
601
612
  const at: i32 = p.headLength
602
613
  let size: i32 = toI32(p.head.length)
603
614
  if (n > size - at) {
615
+ if (size < HTTP1_FIRST_HEAD) {
616
+ size = HTTP1_FIRST_HEAD
617
+ }
604
618
  while (size - at < n) {
605
619
  if (size > 1073741823) {
606
620
  panic("Http1Parser: a head of more than 2^31 - 1 bytes")
@@ -616,9 +630,14 @@ const http1HeadAppend = (p: Http1Parser, from: i32, to: i32): i32 => {
616
630
  }
617
631
  const head: u8[] = p.head
618
632
  const buf: u8[] = p.buf
619
- for (let k: i32 = 0; k < n && at + k < toI32(head.length) && from + k < toI32(buf.length); k += 1) {
620
- if (from + k >= 0) {
621
- head[at + k] = buf[from + k]
633
+ if (n > 0 && from >= 0 && from <= toI32(buf.length) - n && at <= toI32(head.length) - n) {
634
+ // The line lies in `buf` and the head was grown to hold it: one move.
635
+ copyBytes(head, at, buf, from, n)
636
+ } else {
637
+ for (let k: i32 = 0; k < n && at + k < toI32(head.length) && from + k < toI32(buf.length); k += 1) {
638
+ if (from + k >= 0) {
639
+ head[at + k] = buf[from + k]
640
+ }
622
641
  }
623
642
  }
624
643
  p.headLength = at + n