Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP/3 support. #275

Open
jlaine opened this issue Aug 25, 2019 · 38 comments
Open

HTTP/3 support. #275

jlaine opened this issue Aug 25, 2019 · 38 comments
Labels
enhancement New feature or request
Milestone

Comments

@jlaine
Copy link

jlaine commented Aug 25, 2019

It would be nice to start planning support for HTTP/3. aioquic provides a sans-I/O API for HTTP/3 similar to h2 which would make such an integration possible.

The main hurdle is that the connection model is very different to HTTP/1.1 and HTTP/2 : we cannot simply establish a (reader, writer) pair over a TCP connection and pass this to the protocol layer. Between the HTTP/3 layer and the UDP transport, we have a QUIC layer which returns (datagram, addr) tuples indicating where datagrams need to be sent:

https://aioquic.readthedocs.io/en/latest/quic.html#aioquic.quic.connection.QuicConnection.datagrams_to_send

Note that the destination address may change during the connection's lifetime, for example in response to receiving a PreferredAddress from the server during the TLS handshake.

In order to provide a first demonstration, @tomchristie suggested I write a dispatcher which makes use of aioquic's HTTP/3 support. The code is still a bit rough, but it works:

https://github.com/aiortc/aioquic/blob/master/examples/httpx_client.py

This can be run as:

python examples/httpx_client.py https://cloudflare-quic.com/

See also encode/httpcore#173

@jlaine
Copy link
Author

jlaine commented Sep 12, 2019

I'm not too sure how to move this forward. Ideally I'd like to have a WIP pull request which we can rebase / amend until we're happy with the result. However I haven't really got a feel for how the code should integrate with httpx's connection management. Any hints on how to approach this problem?

@sethmlarson
Copy link
Contributor

We'll need to add UDP support to our backend interface, that's definitely step 1.

After that we need to see how we want to implement client storage so we can remember things like Alt-Svc headers between requests.

Next would be landing an HTTP/3 specifier along with the H3Connection dispatcher. I'm not sure how compatible aioquic is with an SSLContext object (probably not at all?) so that will take some tweaking. And because HTTP/3 is known to not work in some routes we'll have to implement "fallback to TCP and forget the H3 alt svc" on the HTTPConnection object somehow.

Anything I'm missing @encode/httpx-maintainers?

@florimondmanca
Copy link
Member

florimondmanca commented Sep 13, 2019

I don't know enough about HTTP/3 to tell (for example, I'm not sure what the differences between QUIC and HTTP/3 and the associated aioquic APIs are?), but the approach highlighted by @sethmlarson sounds sensible to me.

One thing I'm sure of is that we'll need to chunk this into multiple PRs, otherwise we risk ending up with a 1000 LOC+ PR that's a pain for everyone to look at. That way we can more easily track progress too.

We'll need to add UDP support to our backend interface, that's definitely step 1.

Yup, and UDP support is definitely going to be a big chunk.

HTTPX is currently very much coupled to TCP. In particular, right now a given HTTPConnection doesn't know about other connections, but from what you've said it seems like a QUIC "connection" needs to know about the surrounding connections to do packet routing (though it might be a defining property of UDP?), correct? Anyway, there'll be some refactoring needed before supporting UDP to clarify what is TCP-specific and what is not.

After that we need to see how we want to implement client storage so we can remember things like Alt-Svc headers between requests.

I think this should be an improvement over a basic design, right? (Under the hypothesis that remembering Alt-Svc headers will only allow us to skip the protocol switching phase of establishing an HTTP/3 connection.)

So, in terms of planning, I tried to think about the various steps in a bit more detail. I could see something like:

  1. Rename BaseStream / Stream / ConcurrencyBackend.connect() to BaseTCPStream / TCPStream / ConcurrencyBackend.connect_tcp().
  2. Add UDP support. This will most likely include building an UDP version of TCPStream and the associated methods on ConcurrencyBackend. At this point, the resulting updated ConcurrencyBackend (with an asyncio implementation) can be unit-tested against a very basic asyncio UDP server.
  3. Add httpx.dispatch.http3.HTTP3Connection, an HTTP/3 implementation via aioquic. To reduce the scope of this step, we can set some constraints:
    • HTTPConnection.connect() should try to connect via HTTP/3 and create a UDPStream / H3Connection if and only if HTTP/3 is one of the given http_versions (e.g. Client(http_versions=["HTTP/3"]). This way, we don't need to think about Alt-Svc and the switch between TCP and UDP yet.
    • All QUIC connections can be stored on H3Connection directly (similar to how we have one h11.H1Connection on our H1Connection class), without thinking about sharing those connections yet (provided we need to share those at all?).
  4. Add Alt-Svc support: always connect via TCP first, and only if Alt-Svc is received then establish a newH3Connection. (Will this require to modify HTTP11Connection and HTTP2Connection so that they are able to process Alt-Svc header in the response? Is raising an exception in HTTP11Connection.send() and HTTP2Connection.send() and catching it in Connection.send() a sensible enough way of signalling we should reconnect via UDP-HTTP/3?)
  5. Optimize connection making by remembering Alt-Svc headers.
  6. Implement sharing of QUIC connections between HTTPConnections — if relevant?

Some resources I've come across:

@sethmlarson
Copy link
Contributor

Lots of great info @florimondmanca!! 🎉

I'm going to chime in here again at the idea of using AnyIO and adding our stream interface to that?

@florimondmanca
Copy link
Member

florimondmanca commented Sep 13, 2019

I'm going to chime in here again at the idea of using AnyIO and adding our stream interface to that?

As in, using AnyIO (see #296) to provide an ConcurrencyBackend.connect_udp() implementation for all backends on top of their UDP sockets API? I'm not against the idea but it might be a bit too much for this particular issue. It could be a preliminary step but we haven't decided yet on whether we should switch over to AnyIO.

Edit: also, since AnyIO mandates the same "strict context management everywhere" approach than Trio, it's highly probable we'd need to refactor some internals to comply with that requirement. One item in particular is the Stream interface, which trio/AnyIO primarily expose as a context manager, although it's possible to .aclose() manually.

@jlaine
Copy link
Author

jlaine commented Sep 22, 2019

HTTPX is currently very much coupled to TCP. In particular, right now a given HTTPConnection doesn't know about other connections, but from what you've said it seems like a QUIC "connection" needs to know about the surrounding connections to do packet routing (though it might be a defining property of UDP?), correct? Anyway, there'll be some refactoring needed before supporting UDP to clarify what is TCP-specific and what is not.

I don't see why the different connections should have any kind of coupling. The most straightforward approach is going to be opening a distinct UDP socket for every QUIC/HTTP3 connection so there will be a one-to-one mapping between socket and QUIC/HTTP3 connection.

At a high level, an HTTP3 connection is going to be very similar to an HTTP2 connection: you can run any number of requests on top of your connection.

I think this should be an improvement over a basic design, right? (Under the hypothesis that remembering Alt-Svc headers will only allow us to skip the protocol switching phase of establishing an HTTP/3 connection.)

HTTP3 support has landed in cURL and more recently in Chrome/canary so I'd suggest looking into how they handle Alt-Svc.

Some resources I've come across:

You might want to add aioquic's demo HTTP client:

https://github.com/aiortc/aioquic/blob/master/examples/http3_client.py

@tomchristie tomchristie added the enhancement New feature or request label Jan 9, 2020
@tomchristie tomchristie added this to the someday milestone Jul 30, 2020
@tomchristie tomchristie changed the title Adding HTTP/3 support using aioquic HTTP/3 support. Aug 11, 2020
@cdeler
Copy link
Member

cdeler commented Sep 2, 2020

I tried to follow the guide written by @tomchristie above.

The first step was

Rename BaseStream / Stream / ConcurrencyBackend.connect() to BaseTCPStream / TCPStream / ConcurrencyBackend.connect_tcp().

It has been implemented in #339
Having found the PR and the changes, I wanted to look around it and found that neither BaseTCPStream nor TCPStream nor ConcurrencyBackend are presented in httpx/httpcore repos. Moreover the file structure of he repo has significantly changed from the PR.

@florimondmanca , @tomchristie
Do you have a time to provide us with advice, where the process should start? Should it be some sort of new connection to httpcore._async/httpcore._sync with the aioquic backend?

Update
Probably the first step might be to add "open_quic_connection" to existing backends?

@tomchristie
Copy link
Member

tomchristie commented Sep 3, 2020

@cdeler Well, it's about as complex as you could get for a contribution, but if you're up for it then I can certainly put in the guidance for a sensible way to approach it.

The keyword here, as with anything like this, will be incremental. 😀

  • I'd start off with a pull request that just adds an http3 flag to the ConnectionPool configuration, in the same way as the existing http2 flag. It should also be passed through to the HTTPConnection instance. To begin with it shouldn't actually do anything, it should simply be there.
  • The next step could be to "notice" if an HTTP/3 service is advertised. If an HTTP/1.1 or HTTP/2 connection gets a response with an Alt-Svc: h3 header, and if http3=True has been set, then a NotImplementedError exception should be raised.

At that point we've got a stub behaviour for detecting HTTP/3 support, which we can start to iterate on.

@cdeler
Copy link
Member

cdeler commented Sep 3, 2020

The keyword here, as with anything like this, will be incremental. 😀

it's smart behaviour to make incremental changes, I'm happy to try doing that

@tomchristie
Copy link
Member

Slight update - let's scratch the second part of that, actually I think we'll want to use the HTTP/2 frame-type ALTSVC to detect if we should upgrade or not.

We'll use that because we don't want to wait for response headers before we decide on if we should upgrade. The ALTSVC frame can be sent during the opening handshake, before the request itself is made. We'll probably need to end up doing some investigation into exactly when implementations choose to send this frame, before we're able to proceed from there.

@tomchristie
Copy link
Member

It's looking to me like httpx should never end up making an HTTP/3 request on an initial outgoing request, because either:

  • We see the upgrade in an Alt-Svc response headers, in which case we've already sent the request, and started receiving the response, not much point in tearing the connection down.
  • We might potentially see an ALTSVC HTTP/2 frame, but we don't want to block on waiting for that before starting to send a request (since it may not exist).

So I think the best we'll be able to do is storing altsvc information whenever it comes through, and potentially making subsequent requests over HTTP/3 using that information.

@cdeler
Copy link
Member

cdeler commented Sep 6, 2020

@tomchristie

Have you seen this example in the aioquic repo?

@florimondmanca
Copy link
Member

@cdeler — Sure, it was posted by Jeremy (OP) in the issue description. :-) Obviously looks outdated by now since some bits of HTTPX API have changed, but I'm sure it's been discussed earlier in this thread?

@sla-te
Copy link

sla-te commented Jun 16, 2021

Any way to make this work again? Currently getting NotImplementedError

@jlaine
Copy link
Author

jlaine commented Jul 7, 2021

I've submitted aiortc/aioquic#203 to update the httpx demo for recent httpcore versions.

@stale
Copy link

stale bot commented Feb 20, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Feb 20, 2022
@unixfox
Copy link

unixfox commented Feb 20, 2022

Bump still important.

@stale stale bot removed the wontfix label Feb 20, 2022
@stale
Copy link

stale bot commented Mar 25, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Mar 25, 2022
@unixfox
Copy link

unixfox commented Mar 25, 2022

Bump, still relevant.

@stale stale bot removed the wontfix label Mar 25, 2022
@OrionCodeDev
Copy link

Any updates?

@crypt0miester
Copy link

bump

@sla-te
Copy link

sla-te commented Jun 14, 2022

+

@tomchristie
Copy link
Member

tomchristie commented Jun 15, 2022

So, I'm absolutely in favour of someday having support for HTTP/3, yup.

But also... "Any updates?", "bump", "+" aren't valuable in any way.

More interesting would be illustrations of genuine motivations for wanting HTTP/3 support. What context are you using it in & why do you want a client that supports it? I'm interested in cases that go beyond "I want it because it exists".

@unixfox
Copy link

unixfox commented Jun 15, 2022

More interesting would be illustrations of genuine motivations for wanting HTTP/3 support. What context are you using it in & why do you want a client that supports it? I'm interested in cases that go beyond "I want it because it exists".

Well at SearXNG we would want it in order to decrease the loading time when fetching some data from a website that support HTTP3. This would make the experience much snappier for the users of SearXNG.

@crypt0miester
Copy link

crypt0miester commented Jun 15, 2022

So, I'm absolutely in favour of someday having support for HTTP/3, yup.

But also... "Any updates?", "bump", "+" aren't valuable in any way.

More interesting would be illustrations of genuine motivations for wanting HTTP/3 support. What context are you using it in & why do you want a client that supports it? I'm interested in cases that go beyond "I want it because it exists".

I am a solana dev. they just announced that they are using quic as an option.

I am a contributor to the solana python library. I also build stuff on top that.

I tried using aioquic. it works, but the latency is worse than using a regular http/1.1 using httpx.

~1.4s on aioquic, and ~0.7s on httpx. (for two concurrent request from initial request time, until closing the connection.)

I don't know if the reason is because aioquic has to bind to the raw ip socket first.

@tomchristie
Copy link
Member

I tried using aioquic. it works, but the latency is worse than using a regular http/1.1 using httpx.

This doesn't surprise me. My expectation is that in Python, using HTTP/3 will generally be slower, because of the increased complexity.

I can get that it might be useful for us to include for general debugging/tooling purposes, tho.

@crypt0miester
Copy link

I tried using aioquic. it works, but the latency is worse than using a regular http/1.1 using httpx.

This doesn't surprise me. My expectation is that in Python, using HTTP/3 will generally be slower, because of the increased complexity.

I can get that it might be useful for us to include for general debugging/tooling purposes, tho.

once a connection has been established though, it works amazingly. bulk requests is excellent using quic.

@jlaine
Copy link
Author

jlaine commented Jun 16, 2022

I tried using aioquic. it works, but the latency is worse than using a regular http/1.1 using httpx.

This doesn't surprise me. My expectation is that in Python, using HTTP/3 will generally be slower, because of the increased complexity.

I can get that it might be useful for us to include for general debugging/tooling purposes, tho.

This does surprise me, measurements I made show good connection setup time and throughput against most servers.

@crypt0miester
Copy link

I tried using aioquic. it works, but the latency is worse than using a regular http/1.1 using httpx.

This doesn't surprise me. My expectation is that in Python, using HTTP/3 will generally be slower, because of the increased complexity.

I can get that it might be useful for us to include for general debugging/tooling purposes, tho.

This does surprise me, measurements I made show good connection setup time and throughput against most servers.

were your servers local or on the cloud handling hundreds, possibly thousands of requests per second sir? the post requests made is to a solana rpc.

perhaps I am doing something wrong.

@jlaine
Copy link
Author

jlaine commented Jun 17, 2022

were your servers local or on the cloud handling hundreds, possibly thousands of requests per second sir? the post requests made is to a solana rpc.

perhaps I am doing something wrong.

The servers were distant, the results I mention are runs of aioquic's interop suite against a variety of servers, and the acceptance criterion is that the time to download 5MB and 10MB files over HTTP/3 must no more than 10% over HTTP/1.1 or HTTP/2 (using httpx). I'm not sure the server's load is relevant here as we are talking about client performance.

I don't think you're doing anything wrong, there are definitely some reports of initial connection time outliers using aioquic, which can kill the measured performance. I haven't managed to debug these cases yet but I do believe they are fixable.

@tomchristie
Copy link
Member

This does surprise me, measurements I made show good connection setup time and throughput against most servers.

That's a positive snippet of info.

I should be more precise in what I mean to say here. I wouldn't assume that HTTP/3 for httpx would be necessarily be faster. It'll have different performance characteristics, which may be positive in some cases, and negative in others. That's certainly what I've seen with HTTP/1.1 -> HTTP/2.

@crypt0miester
Copy link

thanks team for your efforts.

may I ask if we should expect something? or shall I continue with aioquic?

@tomchristie
Copy link
Member

You'll see updates here if/when someone starts working on integrating HTTP/3 support into httpx.

I expect it'll happen sometime. I'm up for supervising a pull request to get the support in, if someone wants to take it on.

@zanieb
Copy link
Contributor

zanieb commented Sep 26, 2022

I'm interested in trying at moving this forward, but I'm still a beginner in these code bases.

The summary at #275 (comment) seems the most thorough.

Am I correct in understanding that the next step is to detect Alt-Svc in responses and upgrade to HTTP 3? per

Add Alt-Svc support: always connect via TCP first, and only if Alt-Svc is received then establish a new H3Connection

It seems like you had been originally thinking that there should be support for a http3 boolean but it seems your thinking has aligned more with the above approach now?

@tomchristie
Copy link
Member

It seems like you had been originally thinking that there should be support for a http3 boolean but it seems your thinking has aligned more with the above approach now?

Nope. We'd have an http3 boolean and only switch over if it's enabled, and if there's an Alt-Svc response.

However, I'd probably suggest starting this off from a different direction...

Start by testing out @jlaine's httpx client example. - https://github.com/aiortc/aioquic/blob/main/examples/httpx_client.py - is it still working and up to date? I could give some further guidance once we know that's all good and current.

@zanieb
Copy link
Contributor

zanieb commented Sep 27, 2022

It was not working, but it is now in aiortc/aioquic#333 or aiortc/aioquic#314 — whether it's truly all good will take a more thorough suite of tests.

@zanieb
Copy link
Contributor

zanieb commented Nov 6, 2022

aiortc/aioquic#314 is approved and merged, so we have a working example to work from if you want to continue to provide guidance.

@karpetrosyan
Copy link
Member

I have opened a pull request that adds HTTP3 support.
If you are interested, I will appreciate any suggestions and reviews.

PR: encode/httpcore#829

sentrivana added a commit to getsentry/sentry-python that referenced this issue Oct 4, 2024
All our ingest endpoints support HTTP/2 and some even HTTP/3 which are significantly more efficient compared to HTTP/1.1 with multiplexing and, header compression, connection reuse and 0-RTT TLS.

This patch adds an experimental HTTP2Transport with the help of httpcore library. It makes minimal changes to the original HTTPTransport that said with httpcore we should be able to implement asyncio support easily and remove the worker logic (see #2824).

This should also open the door for future HTTP/3 support (see encode/httpx#275).


---------

Co-authored-by: Ivana Kellyer <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests