Run the HTTP/3 tests against a local server, fix two HTTP/3 bugs - #943
Merged
Merged
Conversation
Nine test modules sent HTTP/3 requests to public servers, mostly cloudflare.com, under eunit's default 5 s limit, and accepted a failed connection as a pass. A slow UDP path from a CI runner made them time out, which is the macOS flake seen on #939 and #941. hackney_h3_test_server starts an in-process quic_h3 server on 127.0.0.1 with the test certificate and a few routes (/, /cdn-cgi/trace, /large, /redirect/N, /status/N, POST echo). The tests now assert real results: protocol, status, headers and bodies, redirects followed and the max_redirect limit, Alt-Svc upgrading a TCP origin to HTTP/3 and the blocked-host fallback, and certificate verification against the test CA. The public-server checks in hackney_http3_e2e_SUITE move to an interop group that runs when HACKNEY_H3_INTEROP is set.
After an HTTP/3 streaming upload the response body could not be read:
body/1 returned {error, invalid_state} and stream_body/1 {error,
no_stream}. The stream moved to a receiving state keyed to the
start_response/1 caller, whose call was already answered, and the body
went to it as a second reply before the stream was dropped. The stream
now enters the same pull states as request_streaming, body/1 gains an
HTTP/3 clause that returns the rest of the body at FIN, and
start_response/1 answers at once when the headers came first.
finish_send_body/1 no longer parks its already answered caller, which
made start_response/1 wait forever when the headers arrived early.
hackney_h3 passed cacertfile to quic, which only takes DER cacerts, so
the option was ignored and verification failed as {error, timeout}. It
is now decoded, with the helper hackney_conn already used.
The local HTTP/3 tests get 15 s for a handshake or a response, and run
without session resumption: resuming from a cached ticket can stall the
handshake, reproduced on one scheduler, which is its own issue.
quic_h3 sends the reason with its close event, and hackney only matched
the older shape without one. The message fell into the catch-all and was
dropped: the h3 process stayed alive, the owner heard nothing, and the
caller waited out its timeout. The failure path before HTTP/3 comes up
(bad certificate, TLS alert) already carries a reason in quic 1.10.0, so
a certificate failure surfaced as {error, timeout}; it now surfaces as
{error, {connection_closed, {certificate_invalid, _}}}.
Both shapes are handled: quic =< 1.10.0 still closes without a reason on
its local-close and QUIC-down paths.
quic_h3 reports a response as {response, StreamId, Status, Headers} and
keeps :status in Headers, so prepending the status again handed every
low-level consumer two of them, which RFC 9114 4.3.1 makes a malformed
response. Drop any :status from the list and keep the one quic parsed.
hackney:request/5 was unaffected: parse_response_headers/1 takes the
first :status and filters the pseudo-headers out.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Nine test modules sent HTTP/3 requests to public servers (mostly cloudflare.com) under eunit's default 5 s limit, and accepted a failed connection as a pass. A slow UDP path from a CI runner made them time out: that is the macOS flake on #939 and #941.
hackney_h3_test_serverstarts an in-processquic_h3server on 127.0.0.1 with the test certificates. The tests now assert real results instead of tolerating network errors: protocol, status, headers and bodies, redirect following and themax_redirectlimit, Alt-Svc upgrading a TCP origin to HTTP/3 and the blocked-host fallback, and certificate verification against the test CA. The public-server checks inhackney_http3_e2e_SUITEmove to aninteropgroup that runs withHACKNEY_H3_INTEROP=1.Three HTTP/3 bugs the stricter tests turned up, fixed here:
body/1returned{error, invalid_state}andstream_body/1returned{error, no_stream}, because the body went to thestart_response/1caller as a second reply and the stream was dropped. The stream now enters the same pull states asrequest_streaming,body/1gains an HTTP/3 clause, andstart_response/1answers at once when the headers came first.finish_send_body/1no longer parks its already answered caller, which madestart_response/1hang when the headers arrived early.hackney_h3passedcacertfileto quic, which only takes DERcacerts, so it was ignored and verification failed as{error, timeout}. It is now decoded.quic_h3sends one with its close event and hackney only matched the older shape without it, so the message was dropped, the h3 process stayed alive and the caller waited out its timeout. A handshake that fails on a bad certificate now comes back as{error, {connection_closed, {certificate_invalid, _}}}instead of{error, timeout}. Both shapes are handled, since quic =< 1.10.0 still closes without a reason on two of its paths.Found along the way, left for separate changes:
zero_rttoff. The local tests run with{zero_rtt, false}.hackney:connectlets thegen_statem:calltimeout exit escape instead of returning{error, _}.stream_body/1orbody/1when their stream is reset. Until then quic drops a peer RESET_STREAM on request streams, so that path cannot be tested.