Uh oh!
There was an error while loading. Please reload this page.
Add HTTP3 support - #829
Conversation
e2af16a to
8fc5ff3Compare8fc5ff3 to
bd31869Comparekarpetrosyan
commented
Oct 20, 2023
This is how I see the The goals here are:
I want to keep this pull request as simple as possible, but I'm also thinking about including That is:
|
karpetrosyan
commented
Nov 2, 2023
Any thoughts or ideas, @encode/maintainers? |
lovelydinosaur
commented
Nov 2, 2023
Thanks @karpetrosyan!
Here's my initial high level thoughts...
|
karpetrosyan
commented
Nov 3, 2023
Thank you for reviewing, Tom. Excellent questions; here are my thoughts on that. Review of the current landscape... Which sites currently use HTTP/3 and which browsers can you demonstrate using it? How can someone else observe this?As an example, here are a few large corporations that support HTTP/3: Using this script, you can already test it with httpcore. importhttpcoreimportlogginglogging.basicConfig(level=1)
pool=httpcore.ConnectionPool(http1=False, http3=True)
websites= [
"https://google.com",
"https://youtube.com",
"https://instagram.com",
"https://spotify.com",
"https://cloudflare.com",
]
forwebsiteinwebsites:
response=pool.request("GET", website, extensions={"timeouts": {"connect": 2}})
print(response)
According to Wikipedia, all major browsers support the HTTP/3 protocol.
You can also use this website to determine whether the request was sent over HTTP/3 or HTTP/1.1, and then open the dev tool to view the schema and headers that were sent over the network. To learn more about HTTP/3 state in 2023, visit https://blog.cloudflare.com/http3-usage-one-year-on/. What's the use-case for HTTP/3 in httpx - are there conditions under which it's beneficial to the user?Here are some of the reasons why we should add HTTP/3 support.
How do we intend to maintain the HTTP/3 work alongside the existing HTTP/2 work with a minimal maintenance load?Yes, this is an important question. One of the goals was to implement HTTP/3 with as few differences as possible from HTTP/2. We can assume that What discovery mechanism are browsers currently using for HTTP/3 detection? Is detection over DNS records currently deployed and used?This question has already been discussed in https://github.com/encode/httpx/issues/275. There is also a section in RFC that describes the connection setup process, so you can find more detailed information there. |
seidnerj
commented
Nov 17, 2023
Would love to see HTTP3 support in httpx |
T-256
left a comment
There was a problem hiding this comment.
IMO when enabling multiple HTTP versions, could consider http3 to have higher precedence than two others, since it uses UDP, though could still fallback to TCP.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
T-256
commented
Dec 17, 2023
An alternative example for this change: can_connect_tcp=Trueifself._http3:
try:
from . importAsyncHTTP3Connectionstream=awaitself._connect_http3(request)
self._connection=AsyncHTTP3Connection(
origin=self._origin,
stream=stream,
keepalive_expiry=self._keepalive_expiry,
)
can_connect_tcp=FalseexceptExceptionasexc:
ifnot (self._http1orself._http2):
raiseexcifcan_connect_tcp:
stream=awaitself._connect(request)
ssl_object=stream.get_extra_info("ssl_object")
http2_negotiated= (
ssl_objectisnotNoneandssl_object.selected_alpn_protocol() =="h2"
)
ifhttp2_negotiatedor (self._http2andnotself._http1):
from .http2importAsyncHTTP2Connectionself._connection=AsyncHTTP2Connection(
origin=self._origin,
stream=stream,
keepalive_expiry=self._keepalive_expiry,
)
else:
self._connection=AsyncHTTP11Connection(
origin=self._origin,
stream=stream,
keepalive_expiry=self._keepalive_expiry,
) |
karpetrosyan
commented
Dec 18, 2023
Ugh, let's consider next steps here. HTTP/3 NegotiationThere are at least three approaches we could take to solve this problem:
Let's go over each one and provide some useful links so you can dig deeper. Alt-Svc
HTTP servers, for example, frequently use this In the world of Also, the server may provide additional information with the alternative service, such as an expiration time, which we must respect and avoid using stale information about the alternative service. Here is an example of an This also complicates the use of this approach, so we should account for it. See also: https://http3-explained.haxx.se/en/h3/h3-altsvc
|
I'll leave some key differences between our NotesIf you are unfamiliar with HTTP/3 and HTTP/2, I recommend the following resources: In a nutshell, Unlike in One is the This separation can somewhat help the developer to distinguish the connection layer and the HTTP layer, so we have, for example, In The additional layer is also the reason why there are two "connection" objects in the aioquic package, because unlike in the http2 implementation, where we care only about tcp and http, now we should think about udp, quic, and http. The QUIC layer also helps us to get rid of flow window control, connection setup, and related things that are now handled by the QUIC protocol itself. ChangedEventsFirst, here is how we import HTTP2 events and HTTP3 events importh2.eventsfromaioquic.h3importeventsash3_eventsfromaioquic.quicimporteventsasquic_eventsWe handle five events in HTTP2 implementation; here is how those events look in HTTP3.
Here is the code reference for that part in the http3.py and http2.py RemovedMethod |
rthalley
commented
Jan 2, 2024
Re DNS HTTPS records, dnspython has good support for them, but dnspython also uses httpx (and thus httpcore) for DNS-over-HTTPS, so I'm not completely sure how to deal with the chicken-and-egg mutual module dependency issues, but I'm happy to work with the httpcore team. |
mborsetti
commented
Sep 19, 2024
Where is HTTP/3 support on the release schedule? |
| }, | ||
| ) | ||
| except BaseException as exc: # noqa: PIE786 | ||
| with AsyncShieldCancellation(): |
karpetrosyan
commented
Sep 19, 2024
I’m not sure why the pipeline failed, but the implementation works. I would like to continue working on this, and we need to cover the implementation with tests. What do you think, @encode/maintainers? Do we have any blockers? I would also appreciate a review from @jlaine, if possible. |
karpetrosyan
commented
Sep 19, 2024
I can already see a 10-20% speed boost on my machine compared to our HTTP/2 implementation as well. |
I'm a bit concerned about how the pyopenssl context is configured. I think this would break Generally SSL in httpcore is configured by passing in a SSLContext but this PR seems to bypass that and pass certify.where() |
graingert
commented
Sep 19, 2024
I think the way to do it is move |
I've been thinking about this for a while and using two different contexts for the same httpx session is cryptographically fishy (and probably slow - loading the cert store twice). I've had a quick look at the anyio trio and sync ssl streams and I'm happy to make a PR to make httpcore support either ssl or pyopenssl contexts then we can require a pyopenssl context for http3=True |
graingert
commented
Sep 25, 2024
I've misunderstood how tls in aioquic works, I saw the dep on pyopenssl and made the assumption it's used for TLS. However aioquic uses it's own TLS 1.3 implementation which requires cadata, cafile, capath etc passed in, so we will need to create our own context that wraps the stdlib ssl context and the required parameters for aioquic |
lovelydinosaur
commented
Sep 25, 2024
Pointers? |
| async def _do_handshake(self, request: Request) -> None: | ||
| assert hasattr(self._network_stream, "_addr") | ||
| self._quic_conn.connect(addr=self._network_stream._addr, now=monotonic()) |
There was a problem hiding this comment.
This should use the event loop time, so that trio can use auto jump clock
| return events | ||
| async def _write_outgoing_data(self, request: Request) -> None: |
There was a problem hiding this comment.
This needs to call quic.get_timer() and scheduler a timer so that quic can queue lost datagrams
| ): # pragma: no cover | ||
| from .http3 import AsyncHTTP3Connection | ||
| stream = await self._connect_http3(request) |
There was a problem hiding this comment.
This should be doing happy eyeballs
| raise self._read_exception # pragma: nocover | ||
| try: | ||
| data = await self._network_stream.read(self.READ_NUM_BYTES, timeout) |
There was a problem hiding this comment.
I think you need a background task that's constantly reading any datagrams from the server as they can be sent unsolicited
graingert
commented
Jan 11, 2025
? |
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. |
This pull request tries to add HTTP/3 support.
As we know, the HTTP/2 and HTTP/3 protocols are very similar, except for the protocol they use.
This PR simply follows the steps described below.
connect_udpmethod to "httpcore._backends.base.NetworkBackend".connect_udponly for the synchronous backend (only for now).pyproject.tomlhttpcore/_http3.pyfileHTTP/3in that file, keeping the logic and flow maximum similar to the logic that we are using in _http2.py.To support the
HTTP/3protocol, we need the aioquic package, which is a well-tested and well-designed implementation for theHTTP/3andQUICprotocols.For more details, see the issue in HTTPX, where the author of aioquic provides basic HTTP/3 integration for httpx.
There is a very basic example of how you can use HTTP/3 with the httpcore.
Or with the high-level API: