Skip to content

hackney 4.8.0

Choose a tag to compare

@benoitc benoitc released this 24 Sep 08:14
· 26 commits to master since this release
e41c32d

Fixed

  • hackney:send_request/2 works on HTTP/2 and HTTP/3. Those protocols answer
    a request with the body included, which the function did not handle, so it
    failed with a case_clause. It returns the connection on every protocol
    now, so the response is read with body/1 or pulled with stream_body/1.
  • Several callers reading responses on one HTTP/2 or HTTP/3 connection each
    get their own. The read was resolved from the connection's last stream
    rather than the caller's, so on HTTP/2 all but one caller got
    {error, no_stream}, and on HTTP/3 two callers could be handed each
    other's body.
  • A caller reading an HTTP/3 response with body/1 or stream_body/1 is
    answered when the server resets its stream, instead of waiting for its own
    timeout. Needs quic 2.0.0, the first release to report a peer RESET_STREAM.
  • A pooled HTTP/2 connection no longer closes when the caller that opened it
    exits. It stayed owned by that caller, so its exit failed every other
    caller's request on the connection with {error, closed}. A shared
    connection now has no owner: each stream is tied to its own caller and is
    reset if that caller dies, and the connection closes itself once it has had
    no open stream for the pool timeout (#937, thanks @smartinio).
  • Unregistering a pooled HTTP/2 connection no longer leaks its per-host slot.
    The pool dropped its monitor on the connection, so the slot was never
    released when the connection stopped.
  • A response that crosses a reset of its stream no longer closes the HTTP/2
    connection. h2 dropped the header block of that response without decoding
    it, so the next response on the connection failed with COMPRESSION_ERROR.
  • The response to an HTTP/3 streaming upload can be read. After
    start_response/1, body/1 returned {error, invalid_state} and
    stream_body/1 returned {error, no_stream}: the body went to the
    start_response/1 caller as a second reply and was lost. When the response
    headers arrived before start_response/1 was called, it could wait forever.
  • HTTP/3 response headers carry one :status. quic_h3 passes the status
    separately and keeps it in the header list, and hackney prepended its own,
    so every response reached the low-level {h3, _, {stream_headers, ...}}
    consumer with the pseudo-header twice, which RFC 9114 4.3.1 makes a
    malformed response. Requests through hackney:request/5 were not affected:
    pseudo-headers are filtered before the caller sees them.
  • An HTTP/3 connection that goes away reports why. quic_h3 sends the reason
    with its close event, and the handler only matched the older shape without
    one, so the message was dropped: the caller waited out its own timeout and
    the connection process stayed alive. A handshake that fails on a bad
    certificate or a TLS alert now comes back as that error instead of
    {error, timeout}.
  • hackney_h3:connect/3,4 and hackney_h3:request/5 honor a cacertfile
    option. It was handed to quic, which only takes DER cacerts, so it was
    ignored and verification failed as {error, timeout}. hackney:request/5
    was not affected.
  • Connection-specific headers are dropped from HTTP/2 and HTTP/3 requests.
    A caller's Connection: keep-alive, legal in HTTP/1.1 and banned by
    RFC 9113 8.2.2 and RFC 9114 4.2, reached the header block and every request
    failed before anything was written. hackney picks the protocol through ALPN,
    so the caller cannot know which rules apply: Connection, Keep-Alive,
    Proxy-Connection, Transfer-Encoding and Upgrade are stripped alongside
    Host. TE is kept, being allowed with the value trailers
    (#935, thanks @lennartschoch).
  • A pooled HTTP/1.1 connection handed to a requester no longer closes itself
    under the request. Checkout left the keepalive timer running, armed before
    the readiness probe, so a connection could pass the probe and then fire the
    timer, and the request that followed came back as {error, invalid_state}.
    The timer is disarmed on checkout and re-armed when the connection returns
    to the pool (#934, thanks @lennartschoch).
  • A request that races a peer-initiated close now returns {error, closed}
    instead of {error, invalid_state}. A connection that sees the peer close
    stays alive briefly so late calls get an answer, and during that window every
    call without a handler answered with the generic invalid_state. The same
    race therefore had two answers: {error, closed} once the connection process
    was gone, {error, invalid_state} while it lingered. Callers can now tell a
    closed connection from a misuse of the API (#932, #933, thanks @kpy3).

Changed

  • Update h2 to 0.12.3. 0.12.3 sends the DATA already buffered on a stream
    when a SETTINGS frame raises the initial window, so a request body queued
    against a zero window no longer stalls until an unrelated WINDOW_UPDATE
    arrives.
  • Update quic to 2.0.0 and webtransport to 0.4.7. quic 2.0.0 reports a
    peer resetting a request stream, always sends a reason with its HTTP/3
    close event, and fixes a handshake that could stall when resuming from a
    cached session ticket.