Skip to content

1.18.0 — a client that resolves, negotiates, times out and streams

Choose a tag to compare

@freitasjca freitasjca released this 18 Sep 08:33
· 32 commits to main since this release

chore(release): 1.18.0

A client release. Minor per doc/releasing.md section 2: everything here is
additive - new methods and a new optional parameter - with nothing removed and
no wire-format change.

49fdac5 CL1 ConnectToHost resolves host names and IPv6 on all four
platform arms. It took an IPv4 literal only, and two of the
three branches never checked the parse, so a host NAME became
a connect to 0.0.0.0 or 255.255.255.255 rather than an error.
fc02cb4 CL2a :scheme follows the transport. Every request over TLS
advertised :scheme: http (RFC 7540 8.1.2.3).
1779040 CL2b ALPN is offered by Connect itself, and refusal names what
happened instead of rendering it as 'server selected ""'.
CL2c The request timeout expires. It was documented and checked,
but only BETWEEN reads: DoRead went straight to a blocking
recv, so a peer that accepted and then said nothing parked the
client permanently. Real in the source, inert at runtime.
CL3a Incremental response delivery. BeginRequest(...,
AStreamResponse := True) opts one stream out of whole-body
buffering; ReadChunk drains it as it arrives.

New API, all additive:

TNghttp2Client.ReadChunk(AStreamId; var ABuffer; ACount, ATimeoutMS)
> 0 bytes copied = 0 end of stream < 0 timed out, still open
TNghttp2Client.BeginRequest(..., AStreamResponse: Boolean = False)
TTlsClientConnection.ReadTimeoutMS (0 = block forever, the old behaviour)

Existing callers are unaffected: the new parameter defaults to False and
ReadTimeoutMS defaults to 0, so a stream that does not ask for incremental
delivery or a deadline behaves exactly as before.

Two behaviour changes worth knowing about, neither of which alters the wire
format:

  • A TLS connection whose peer does not negotiate h2 is now REFUSED at Connect
    with a specific message. It was already refused; the message was misleading.
  • TakeResponse RAISES on a streaming stream that still has unread bytes,
    rather than returning an empty Body that looks like a server which sent
    nothing.

Known limits, documented in the code rather than left to be discovered:

  • Connect still has no timeout of its own. ConnectToHost is a blocking
    connect(), so a blackholed SYN hangs one layer earlier than this fixes.
  • SocketWaitImpl reports any fd >= 1024 as readable WITHOUT waiting, because
    select's fd_set cannot represent it, so the read timeout degrades under
    heavy descriptor pressure.
  • ReadChunk delivers incrementally, but arrival-before-END_STREAM is only
    observable under async dispatch, where the host owns the worker pool.

Gates, both toolchains, FPC trunk 3.3.1 and Delphi 12 dcc64:

Nghttp2AlpnMismatch 7/0 and 3/0 first execution of the ALPN error path
Nghttp2ReadTimeout 6/0 1503-1515 ms against a 1500 ms budget
Nghttp2StreamRead 14/0/1 the skip is structural and stated

Whole suite green around them on both compilers. The generator did not change,
so the corpus sweeps are carried forward from 1.17.0 rather than re-run:
compile-check.sh --all 7,230/7,301 with 0 emitter defects, corpus-check.sh
7,287 accept / 14 refuse / 0 unknown / 0 error.

The provider floor stays at >=1.10.0 per section 6 - horse-provider-nghttp2
does not call the new API yet, and raising a floor gratuitously forces
downstream upgrades for no benefit.