@amritk/nish-x86_64-linux 0.28.0 → 0.30.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.
@@ -70,7 +70,11 @@
70
70
  * most once a second; a 1xx carries none. The pool's slots share one, so a
71
71
  * busy server formats a second once, not once a slot. A program that sets its
72
72
  * own `date` field sends that one instead, so a response has one `Date`.
73
- * `date.pin(unixMs)` fixes it, for a test that compares bytes.
73
+ * `Http1Server` reads the clock once per serve call — from `readable` or
74
+ * `writable` to the `flush` that ends it — and every response written in
75
+ * between carries that reading; one written outside a serve call, an answer
76
+ * given later, reads the clock then. `date.pin(unixMs)` fixes it, for a test
77
+ * that compares bytes.
74
78
  *
75
79
  * **Refusals.** Every refusal of `nish/net/http1`'s parser — the smuggling
76
80
  * shapes, a `Content-Length` with a `Transfer-Encoding`, two that disagree,
@@ -118,11 +122,12 @@ import {
118
122
  HTTP_CODING_GZIP,
119
123
  HTTP_CODING_IDENTITY,
120
124
  HTTP_CODING_NONE,
125
+ HTTP_DEFAULT_MSS,
121
126
  HTTP_OFFER_GZIP,
122
127
  HttpCompressor,
123
128
  httpAcceptEncoding,
124
129
  } from "nish/net/http-compress"
125
- import { netClose, netRead, netWrite, tcpAccept } from "nish:net"
130
+ import { netClose, netMss, netRead, netWrite, tcpAccept } from "nish:net"
126
131
  import {
127
132
  HTTP1_BODY,
128
133
  HTTP1_CHUNKED,
@@ -274,7 +279,8 @@ const http1ServerCopy = (out: u8[], at: i32, src: u8[], off: i32, n: i32): i32 =
274
279
  * it calls, `httpDateWrite`, stays in `nish/net/http-fields`, which compiles
275
280
  * for wasm32 (the split `nish/crypto/x509` and `x509-random` make). `pin`
276
281
  * replaces the clock with one instant, which is how a test asserts a response
277
- * byte for byte.
282
+ * byte for byte. `hold` reads the clock once for a run of responses, a serve
283
+ * call's, until `unhold`; `pin` wins over it.
278
284
  */
279
285
  export class HttpDate {
280
286
  /** The IMF-fixdate of `second`: what `current` answers, made once. */
@@ -283,9 +289,14 @@ export class HttpDate {
283
289
  second: i64 = -1
284
290
  /** The instant `pin` fixed, in milliseconds since 1970, while `pinned`. */
285
291
  pinnedMs: i64 = 0
292
+ /** The clock reading `hold` took, in milliseconds since 1970, while `held`. */
293
+ heldMs: i64 = 0
286
294
  /** How many times `bytes` has been rewritten. */
287
295
  rewrites: i64 = 0
296
+ /** How many times the wall clock has been read. */
297
+ reads: i64 = 0
288
298
  pinned: boolean = false
299
+ held: boolean = false
289
300
 
290
301
  /** A date on the wall clock, with nothing formatted yet. */
291
302
  constructor() {
@@ -300,7 +311,10 @@ export class HttpDate {
300
311
  * or past 9999 answers the nearer of the two ends.
301
312
  */
302
313
  current(): u8[] {
303
- const ms: i64 = this.pinned ? this.pinnedMs : toI64(Date.now())
314
+ let ms: i64 = this.pinnedMs
315
+ if (!this.pinned) {
316
+ ms = this.held ? this.heldMs : this.now()
317
+ }
304
318
  let second: i64 = ms / 1000
305
319
  if (ms < 0) {
306
320
  second = 0
@@ -334,6 +348,34 @@ export class HttpDate {
334
348
  this.pinned = false
335
349
  }
336
350
 
351
+ /**
352
+ * Reads the wall clock once and makes `current` answer that reading until
353
+ * `unhold`, so the responses of one serve call cost one read. A pinned date
354
+ * reads nothing. A second `hold` takes a fresh reading.
355
+ */
356
+ hold(): void {
357
+ if (!this.pinned) {
358
+ this.heldMs = this.now()
359
+ }
360
+ this.held = true
361
+ }
362
+
363
+ /** Puts `current` back on a clock read per call, for a response written outside a serve call. */
364
+ unhold(): void {
365
+ this.held = false
366
+ }
367
+
368
+ /** The wall clock, in milliseconds since 1970. */
369
+ now(): i64 {
370
+ this.reads += 1
371
+ return toI64(Date.now())
372
+ }
373
+
374
+ /** How many times the wall clock has been read: once per `hold`, and once per `current` outside one. */
375
+ clockReads(): i64 {
376
+ return this.reads
377
+ }
378
+
337
379
  /** How many times the bytes have been rewritten: one per second that `current` was asked for, at most. */
338
380
  formats(): i64 {
339
381
  return this.rewrites
@@ -493,7 +535,11 @@ export class Http1Connection {
493
535
  * put another, to share it or to pin its clock.
494
536
  */
495
537
  date: HttpDate
496
- /** The request parser: its head (`methodIs`, `targetIs`, `header`, spans) is the last `H1_REQUEST`'s. */
538
+ /**
539
+ * The request parser: its head (`methodIs`, `targetIs`, `header`, spans) is
540
+ * the current request's from its `H1_REQUEST` until its response is done,
541
+ * `H1_END` included, however much of a pipelined request is fed behind it.
542
+ */
497
543
  parser: Http1Parser
498
544
  /**
499
545
  * The frame decoder: made when the slot first switches to WebSocket, so a
@@ -562,10 +608,14 @@ export class Http1Connection {
562
608
  /** A head has begun to arrive and is not read whole yet: `headSince` is its deadline's start. */
563
609
  headTimed: boolean = false
564
610
  /**
565
- * This connection's TCP maximum segment size, for `respondEncoded` under
566
- * `HttpCompressor.segmentMode`; 0, the default, uses the compressor's `mss`.
611
+ * The TCP maximum segment size the program sets for this connection, for
612
+ * `respondEncoded` under `HttpCompressor.segmentMode`; 0, the default,
613
+ * leaves it to the compressor and the socket (`segmentMss`). Each accept
614
+ * puts it back to 0.
567
615
  */
568
616
  mss: i32 = 0
617
+ /** The MSS `Http1Server.accept` read from this connection's socket (`netMss`), or 0 when it reports none. */
618
+ socketMss: i32 = 0
569
619
 
570
620
  /** A connection under `config`. A cap outside what the connection can work with panics. */
571
621
  constructor(config: Http1Config) {
@@ -649,6 +699,8 @@ export class Http1Connection {
649
699
  this.inputEnded = false
650
700
  this.headTimed = false
651
701
  this.headSince = 0
702
+ this.mss = 0
703
+ this.socketMss = 0
652
704
  this.release()
653
705
  this.park()
654
706
  }
@@ -1225,7 +1277,8 @@ export class Http1Connection {
1225
1277
  * `Accept-Encoding` with gzip offered (`httpAcceptEncoding`):
1226
1278
  * `HTTP_CODING_GZIP`, `HTTP_CODING_IDENTITY` (also with no such field), or
1227
1279
  * `HTTP_CODING_NONE` when the client refuses identity and does not take
1228
- * gzip. Allocates nothing.
1280
+ * gzip. Allocates nothing. Read at `H1_REQUEST`, `H1_BODY` or `H1_END`
1281
+ * alike: the head stays the request's until its response is done.
1229
1282
  */
1230
1283
  acceptEncoding(): i32 {
1231
1284
  return this.acceptEncodingOf(HTTP_OFFER_GZIP)
@@ -1255,6 +1308,22 @@ export class Http1Connection {
1255
1308
  return httpAcceptEncoding(parser.head, start, end, offered)
1256
1309
  }
1257
1310
 
1311
+ /**
1312
+ * The segment `respondEncoded` counts in under `enc.segmentMode`: this
1313
+ * connection's `mss` when the program set it, else `enc.mss` when the
1314
+ * program set it to other than `HTTP_DEFAULT_MSS`, else the socket's
1315
+ * (`socketMss`), else `HTTP_DEFAULT_MSS`.
1316
+ */
1317
+ segmentMss(enc: HttpCompressor): i32 {
1318
+ if (this.mss > 0) {
1319
+ return this.mss
1320
+ }
1321
+ if (enc.mss > 0 && enc.mss !== HTTP_DEFAULT_MSS) {
1322
+ return enc.mss
1323
+ }
1324
+ return this.socketMss > 0 ? this.socketMss : HTTP_DEFAULT_MSS
1325
+ }
1326
+
1258
1327
  /**
1259
1328
  * Answers the current request with all of `body[off .. off + len)` in one
1260
1329
  * call: `respond`, `write` and `end`, the body compressed by `enc` when
@@ -1263,10 +1332,12 @@ export class Http1Connection {
1263
1332
  * otherwise sent as it is; `Vary: Accept-Encoding` while `enc.vary` is set
1264
1333
  * and the choice turned on the request (`HttpCompressor.varies`). Under
1265
1334
  * `enc.segmentMode` the compressed response goes out only when, head
1266
- * included, it takes fewer segments of `mss` bytes (this connection's, or
1267
- * `enc.mss`) than the body as it is; `enc.decision` says what was chosen.
1268
- * A request that refuses identity (`identityRefused`) is sent gzip whatever
1269
- * the length, the saving or the segments (`enc.compressAnyway`).
1335
+ * included, it takes fewer segments of `segmentMss(enc)` bytes than the
1336
+ * body as it is, and a response that fits one segment as it is goes so
1337
+ * without compressing (`enc.oneSegment`); `enc.decision`
1338
+ * says what was chosen. A request that refuses identity (`identityRefused`)
1339
+ * is sent gzip whatever the length, the saving or the segments
1340
+ * (`enc.compressAnyway`).
1270
1341
  * The program's fields may not name `Content-Encoding` or, with
1271
1342
  * `enc.vary`, `Vary`. The whole response is written or none of
1272
1343
  * it: `H1_AGAIN` when the output has no room for it yet (`H1_WRITE` says
@@ -1290,16 +1361,6 @@ export class Http1Connection {
1290
1361
  return H1_CLOSED
1291
1362
  }
1292
1363
  const required: boolean = coding === HTTP_CODING_GZIP && this.identityRefused()
1293
- let packed: i32 = required
1294
- ? enc.compressAnyway(coding, body, off, len)
1295
- : enc.compress(coding, body, off, len)
1296
- let n: i32 = len
1297
- if (packed >= 0) {
1298
- enc.addFields(names, values)
1299
- n = packed
1300
- } else {
1301
- enc.identityFields(names, values)
1302
- }
1303
1364
  // `respond` and the body together, or neither: what `respond` sets is
1304
1365
  // put back when the body does not fit behind the head. `room` first, so
1305
1366
  // that what was sent is already moved out of the way and the `outputEnd`
@@ -1307,15 +1368,43 @@ export class Http1Connection {
1307
1368
  this.room()
1308
1369
  const outputEnd: i32 = this.outputEnd
1309
1370
  const closeAfter: boolean = this.closeAfter
1310
- let r: i32 = this.respond(status, enc.names, enc.values, n)
1311
- if (r !== 0) {
1312
- return r
1371
+ let packed: i32 = -1
1372
+ let n: i32 = len
1373
+ let r: i32 = 0
1374
+ let queued: boolean = false
1375
+ const mss: i32 = this.segmentMss(enc)
1376
+ if (!required && enc.segmentMode && enc.oneSegment(coding, len, mss)) {
1377
+ // A response that fits one segment as it is cannot take fewer
1378
+ // compressed, so it goes as it is with no encoder run, unless its head
1379
+ // pushes it past the segment: then it is taken back and decided below.
1380
+ enc.identityFields(names, values)
1381
+ r = this.respond(status, enc.names, enc.values, len)
1382
+ if (r !== 0) {
1383
+ return r
1384
+ }
1385
+ queued = this.outputEnd - outputEnd + len <= enc.segmentSize(mss)
1386
+ if (!queued) {
1387
+ this.unrespond(outputEnd, closeAfter)
1388
+ }
1389
+ }
1390
+ if (!queued) {
1391
+ packed = required ? enc.compressAnyway(coding, body, off, len) : enc.compress(coding, body, off, len)
1392
+ if (packed >= 0) {
1393
+ enc.addFields(names, values)
1394
+ n = packed
1395
+ } else {
1396
+ enc.identityFields(names, values)
1397
+ }
1398
+ r = this.respond(status, enc.names, enc.values, n)
1399
+ if (r !== 0) {
1400
+ return r
1401
+ }
1313
1402
  }
1314
1403
  if (packed >= 0 && enc.segmentMode && !required) {
1315
1404
  // The head just written, and the one the body as it is would get.
1316
1405
  const head: i32 = this.outputEnd - outputEnd
1317
1406
  const plain: i32 = enc.http1IdentityHead(head, packed, len)
1318
- if (!enc.fewerSegments(plain + len, head + packed, this.mss)) {
1407
+ if (!enc.fewerSegments(plain + len, head + packed, mss)) {
1319
1408
  this.unrespond(outputEnd, closeAfter)
1320
1409
  enc.identityFields(names, values)
1321
1410
  packed = -1
@@ -1525,7 +1614,11 @@ export class Http1Server {
1525
1614
  * accepted into, and slot 0's once `connection` has asked for it.
1526
1615
  */
1527
1616
  connections: Http1Connection[]
1528
- /** The `Date` every slot's responses carry, formatted once a second for all of them. `date.pin` fixes its clock. */
1617
+ /**
1618
+ * The `Date` every slot's responses carry, formatted once a second for all
1619
+ * of them and read from the clock once per serve call: `readable` and
1620
+ * `writable` hold a reading and `flush` lets it go. `date.pin` fixes its clock.
1621
+ */
1529
1622
  date: HttpDate
1530
1623
  /**
1531
1624
  * Each slot's descriptor, or -1 while it is free, for the slots accepted
@@ -1669,18 +1762,27 @@ export class Http1Server {
1669
1762
  this.fds[k] = fd
1670
1763
  this.armed[k] = H1_WANT_READ
1671
1764
  conn.restart()
1765
+ // Once a connection, not a request, so a keep-alive request costs no
1766
+ // syscall: a path whose MSS moves after the handshake is not followed.
1767
+ const mss: i32 = netMss(fd)
1768
+ if (mss > 0) {
1769
+ conn.socketMss = mss
1770
+ }
1672
1771
  conn.lastActive = monotonicNanos()
1673
1772
  return k
1674
1773
  }
1675
1774
 
1676
1775
  /**
1677
1776
  * The socket of `slot` is readable: reads it while the connection has room
1678
- * and the socket has more, then sends. Answers the interest.
1777
+ * and the socket has more, then sends. Answers the interest. It begins a
1778
+ * serve call, which the program's `flush` of the slot ends: the responses
1779
+ * written in between carry one clock reading as their `Date`.
1679
1780
  */
1680
1781
  readable(slot: i32): i32 {
1681
1782
  if (!this.holds(slot)) {
1682
1783
  return H1_DONE
1683
1784
  }
1785
+ this.date.hold()
1684
1786
  const conn: Http1Connection = this.connections[slot]
1685
1787
  const fd: i32 = this.fds[slot]
1686
1788
  let moved: boolean = false
@@ -1714,12 +1816,16 @@ export class Http1Server {
1714
1816
  conn.lastActive = now
1715
1817
  conn.headArrived(now)
1716
1818
  }
1717
- return this.flush(slot)
1819
+ return this.settle(slot)
1718
1820
  }
1719
1821
 
1720
- /** The socket of `slot` is writable. Answers the interest. */
1822
+ /** The socket of `slot` is writable: sends, and begins a serve call as `readable` does. Answers the interest. */
1721
1823
  writable(slot: i32): i32 {
1722
- return this.flush(slot)
1824
+ if (!this.holds(slot)) {
1825
+ return H1_DONE
1826
+ }
1827
+ this.date.hold()
1828
+ return this.settle(slot)
1723
1829
  }
1724
1830
 
1725
1831
  /**
@@ -1743,9 +1849,16 @@ export class Http1Server {
1743
1849
  /**
1744
1850
  * Sends what `slot`'s connection holds until the socket would block, and
1745
1851
  * answers the interest: `H1_DONE` once the connection is over and sent,
1746
- * otherwise read while it has room and write while it holds bytes.
1852
+ * otherwise read while it has room and write while it holds bytes. It ends
1853
+ * the serve call, so a response written after it reads the clock anew.
1747
1854
  */
1748
1855
  flush(slot: i32): i32 {
1856
+ this.date.unhold()
1857
+ return this.settle(slot)
1858
+ }
1859
+
1860
+ /** `flush` within a serve call: sends, answers the interest, and keeps the clock reading. */
1861
+ settle(slot: i32): i32 {
1749
1862
  if (!this.holds(slot)) {
1750
1863
  return H1_DONE
1751
1864
  }