boring-http 2.2.0__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {boring_http-2.2.0 → boring_http-2.3.0}/PKG-INFO +5 -5
- {boring_http-2.2.0 → boring_http-2.3.0}/README.md +2 -2
- {boring_http-2.2.0 → boring_http-2.3.0}/pyproject.toml +3 -3
- {boring_http-2.2.0 → boring_http-2.3.0}/pyproject.toml.orig +3 -3
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/client.py +16 -4
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/http2.py +5 -1
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/http3.py +11 -3
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/__init__.py +0 -0
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/codings.py +0 -0
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/cookies.py +0 -0
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/errors.py +0 -0
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/hpack.py +0 -0
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/hpack_tables.py +0 -0
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/http1.py +0 -0
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/message.py +0 -0
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/pool.py +0 -0
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/public_suffixes.py +0 -0
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/py.typed +0 -0
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/qpack.py +0 -0
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/qpack_tables.py +0 -0
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/quic/__init__.py +0 -0
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/quic/connection.py +0 -0
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/quic/packet.py +0 -0
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/quic/protocol.py +0 -0
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/quic/recovery.py +0 -0
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/quic/streams.py +0 -0
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/revocation.py +0 -0
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/timing.py +0 -0
- {boring_http-2.2.0 → boring_http-2.3.0}/src/boring_http/websocket.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: boring-http
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Asyncio HTTP client on BoringSSL: HTTP/1.1, HTTP/2 and HTTP/3 over QUIC v1 and v2, WebSocket on all three, HTTP and SOCKS proxies, headers in your order
|
|
5
|
-
Requires-Dist: boring-ssl2>=1.
|
|
6
|
-
Requires-Dist: ssl2>=2.0
|
|
5
|
+
Requires-Dist: boring-ssl2>=1.3.0
|
|
6
|
+
Requires-Dist: ssl2>=2.1.0
|
|
7
7
|
Requires-Python: >=3.14
|
|
8
8
|
Project-URL: Repository, https://github.com/baobien-dev/boring-http
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
@@ -42,7 +42,7 @@ async def main() -> None:
|
|
|
42
42
|
asyncio.run(main())
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
Certificates are checked against the system trust store, or against `trust_store=TrustStore.from_file(...)` from `ssl2`; `verify=False` turns the check off for a test server. Revocation is checked against the CRL the certificate names: fetched over HTTP, verified with the issuer from the chain, cached until its next update, and a listed certificate is refused with `RevocationError`. `revocation=Revocation.STRICT` also refuses when no valid CRL can be had, `Revocation.NONE` skips the check, and the default `Revocation.SOFT` lets such a connection through, as browsers do. The check covers the origin's certificate and an HTTPS proxy's own certificate on a plain-HTTP request through it; the proxy's certificate on a TLS tunnel is verified but not checked for revocation. `certificate_chain=SigningChain(...)` from `ssl2` presents a client certificate over TCP and QUIC. `check_peer=` is awaited with the peer's chain in DER, leaf first, and its authority after the same checks; raising from it refuses the connection, which is where a key pin goes. Connections are pooled per origin and proxy under `max_connections` and `max_connections_per_host`; one that has served nothing for `max_idle_time` seconds, 90 by default, is closed instead of reused, and `None` keeps it until the server closes it. Headers go on the wire in the order given, client headers first and then the request's own. `Host` leads an HTTP/1.1 request; HTTP/2 and HTTP/3 lowercase the names and drop the connection-specific ones, nothing else changes. `params`, `json`, `data`, `content`, `files`, `auth` and `cookies` are accepted by `get`, `post`, `put`, `patch`, `delete`, `head`, `options` and `request`.
|
|
45
|
+
Certificates are checked against the system trust store, or against `trust_store=TrustStore.from_file(...)` from `ssl2`; `verify=False` turns the check off for a test server. Revocation is checked against the CRL the certificate names: fetched over HTTP, verified with the issuer from the chain, cached until its next update, and a listed certificate is refused with `RevocationError`. `revocation=Revocation.STRICT` also refuses when no valid CRL can be had, `Revocation.NONE` skips the check, and the default `Revocation.SOFT` lets such a connection through, as browsers do. The check covers the origin's certificate and an HTTPS proxy's own certificate on a plain-HTTP request through it; the proxy's certificate on a TLS tunnel is verified but not checked for revocation. `certificate_chain=SigningChain(...)` from `ssl2` presents a client certificate over TCP and QUIC. `check_peer=` is awaited with the peer's chain in DER, leaf first, and its authority after the same checks; raising from it refuses the connection, which is where a key pin goes. `key_log="keys.txt"` appends the secrets of every TLS and QUIC handshake in the NSS key log format, for Wireshark. `resolve={("example.com", 443): ("203.0.113.7", 443)}` sends a connection without a proxy to that address instead of resolving the name, while SNI and certificate verification keep the name from the URL. Connections are pooled per origin and proxy under `max_connections` and `max_connections_per_host`; one that has served nothing for `max_idle_time` seconds, 90 by default, is closed instead of reused, and `None` keeps it until the server closes it. Headers go on the wire in the order given, client headers first and then the request's own. `Host` leads an HTTP/1.1 request; HTTP/2 and HTTP/3 lowercase the names and drop the connection-specific ones, nothing else changes. `params`, `json`, `data`, `content`, `files`, `auth` and `cookies` are accepted by `get`, `post`, `put`, `patch`, `delete`, `head`, `options` and `request`.
|
|
46
46
|
|
|
47
47
|
## Responses
|
|
48
48
|
|
|
@@ -455,7 +455,7 @@ async def main() -> None:
|
|
|
455
455
|
asyncio.run(main())
|
|
456
456
|
```
|
|
457
457
|
|
|
458
|
-
Over HTTP/1.1 this is the Upgrade handshake. Over HTTP/2 and HTTP/3 it is Extended CONNECT when the server advertises `SETTINGS_ENABLE_CONNECT_PROTOCOL`, otherwise the client opens an HTTP/1.1 connection for it. `subprotocols=[...]` offers subprotocols; `websocket.subprotocol` is the one chosen. The `total` of `timeout` covers the handshake only; afterwards `read` and `write` still limit each wait, so `Timeout(read=None)` lets `receive()` wait through any silence.
|
|
458
|
+
Over HTTP/1.1 this is the Upgrade handshake. Over HTTP/2 and HTTP/3 it is Extended CONNECT when the server advertises `SETTINGS_ENABLE_CONNECT_PROTOCOL`, otherwise the client opens an HTTP/1.1 connection for it. Each WebSocket has a connection of its own, which `close()` closes after the closing handshake. `subprotocols=[...]` offers subprotocols; `websocket.subprotocol` is the one chosen. The `total` of `timeout` covers the handshake only; afterwards `read` and `write` still limit each wait, so `Timeout(read=None)` lets `receive()` wait through any silence.
|
|
459
459
|
|
|
460
460
|
## uvloop
|
|
461
461
|
|
|
@@ -32,7 +32,7 @@ async def main() -> None:
|
|
|
32
32
|
asyncio.run(main())
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
Certificates are checked against the system trust store, or against `trust_store=TrustStore.from_file(...)` from `ssl2`; `verify=False` turns the check off for a test server. Revocation is checked against the CRL the certificate names: fetched over HTTP, verified with the issuer from the chain, cached until its next update, and a listed certificate is refused with `RevocationError`. `revocation=Revocation.STRICT` also refuses when no valid CRL can be had, `Revocation.NONE` skips the check, and the default `Revocation.SOFT` lets such a connection through, as browsers do. The check covers the origin's certificate and an HTTPS proxy's own certificate on a plain-HTTP request through it; the proxy's certificate on a TLS tunnel is verified but not checked for revocation. `certificate_chain=SigningChain(...)` from `ssl2` presents a client certificate over TCP and QUIC. `check_peer=` is awaited with the peer's chain in DER, leaf first, and its authority after the same checks; raising from it refuses the connection, which is where a key pin goes. Connections are pooled per origin and proxy under `max_connections` and `max_connections_per_host`; one that has served nothing for `max_idle_time` seconds, 90 by default, is closed instead of reused, and `None` keeps it until the server closes it. Headers go on the wire in the order given, client headers first and then the request's own. `Host` leads an HTTP/1.1 request; HTTP/2 and HTTP/3 lowercase the names and drop the connection-specific ones, nothing else changes. `params`, `json`, `data`, `content`, `files`, `auth` and `cookies` are accepted by `get`, `post`, `put`, `patch`, `delete`, `head`, `options` and `request`.
|
|
35
|
+
Certificates are checked against the system trust store, or against `trust_store=TrustStore.from_file(...)` from `ssl2`; `verify=False` turns the check off for a test server. Revocation is checked against the CRL the certificate names: fetched over HTTP, verified with the issuer from the chain, cached until its next update, and a listed certificate is refused with `RevocationError`. `revocation=Revocation.STRICT` also refuses when no valid CRL can be had, `Revocation.NONE` skips the check, and the default `Revocation.SOFT` lets such a connection through, as browsers do. The check covers the origin's certificate and an HTTPS proxy's own certificate on a plain-HTTP request through it; the proxy's certificate on a TLS tunnel is verified but not checked for revocation. `certificate_chain=SigningChain(...)` from `ssl2` presents a client certificate over TCP and QUIC. `check_peer=` is awaited with the peer's chain in DER, leaf first, and its authority after the same checks; raising from it refuses the connection, which is where a key pin goes. `key_log="keys.txt"` appends the secrets of every TLS and QUIC handshake in the NSS key log format, for Wireshark. `resolve={("example.com", 443): ("203.0.113.7", 443)}` sends a connection without a proxy to that address instead of resolving the name, while SNI and certificate verification keep the name from the URL. Connections are pooled per origin and proxy under `max_connections` and `max_connections_per_host`; one that has served nothing for `max_idle_time` seconds, 90 by default, is closed instead of reused, and `None` keeps it until the server closes it. Headers go on the wire in the order given, client headers first and then the request's own. `Host` leads an HTTP/1.1 request; HTTP/2 and HTTP/3 lowercase the names and drop the connection-specific ones, nothing else changes. `params`, `json`, `data`, `content`, `files`, `auth` and `cookies` are accepted by `get`, `post`, `put`, `patch`, `delete`, `head`, `options` and `request`.
|
|
36
36
|
|
|
37
37
|
## Responses
|
|
38
38
|
|
|
@@ -445,7 +445,7 @@ async def main() -> None:
|
|
|
445
445
|
asyncio.run(main())
|
|
446
446
|
```
|
|
447
447
|
|
|
448
|
-
Over HTTP/1.1 this is the Upgrade handshake. Over HTTP/2 and HTTP/3 it is Extended CONNECT when the server advertises `SETTINGS_ENABLE_CONNECT_PROTOCOL`, otherwise the client opens an HTTP/1.1 connection for it. `subprotocols=[...]` offers subprotocols; `websocket.subprotocol` is the one chosen. The `total` of `timeout` covers the handshake only; afterwards `read` and `write` still limit each wait, so `Timeout(read=None)` lets `receive()` wait through any silence.
|
|
448
|
+
Over HTTP/1.1 this is the Upgrade handshake. Over HTTP/2 and HTTP/3 it is Extended CONNECT when the server advertises `SETTINGS_ENABLE_CONNECT_PROTOCOL`, otherwise the client opens an HTTP/1.1 connection for it. Each WebSocket has a connection of its own, which `close()` closes after the closing handshake. `subprotocols=[...]` offers subprotocols; `websocket.subprotocol` is the one chosen. The `total` of `timeout` covers the handshake only; afterwards `read` and `write` still limit each wait, so `Timeout(read=None)` lets `receive()` wait through any silence.
|
|
449
449
|
|
|
450
450
|
## uvloop
|
|
451
451
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "boring-http"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.3.0"
|
|
4
4
|
description = "Asyncio HTTP client on BoringSSL: HTTP/1.1, HTTP/2 and HTTP/3 over QUIC v1 and v2, WebSocket on all three, HTTP and SOCKS proxies, headers in your order"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.14"
|
|
7
7
|
dependencies = [
|
|
8
|
-
"boring-ssl2>=1.
|
|
9
|
-
"ssl2>=2.0
|
|
8
|
+
"boring-ssl2>=1.3.0",
|
|
9
|
+
"ssl2>=2.1.0",
|
|
10
10
|
]
|
|
11
11
|
|
|
12
12
|
[project.urls]
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "boring-http"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.3.0"
|
|
4
4
|
description = "Asyncio HTTP client on BoringSSL: HTTP/1.1, HTTP/2 and HTTP/3 over QUIC v1 and v2, WebSocket on all three, HTTP and SOCKS proxies, headers in your order"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.14"
|
|
7
7
|
dependencies = [
|
|
8
|
-
"boring-ssl2>=1.
|
|
9
|
-
"ssl2>=2.0
|
|
8
|
+
"boring-ssl2>=1.3.0",
|
|
9
|
+
"ssl2>=2.1.0",
|
|
10
10
|
]
|
|
11
11
|
|
|
12
12
|
[project.urls]
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import base64
|
|
2
|
+
import os
|
|
2
3
|
from collections.abc import (
|
|
3
4
|
AsyncGenerator,
|
|
4
5
|
AsyncIterable,
|
|
@@ -117,6 +118,8 @@ class Client:
|
|
|
117
118
|
revocation: Revocation = Revocation.SOFT,
|
|
118
119
|
certificate_chain: SigningChain | None = None,
|
|
119
120
|
check_peer: Callable[[tuple[bytes, ...], str], Awaitable[None]] | None = None,
|
|
121
|
+
key_log: str | os.PathLike[str] | None = None,
|
|
122
|
+
resolve: Mapping[tuple[str, int], tuple[str, int]] | None = None,
|
|
120
123
|
cookies: CookieJar | None = None,
|
|
121
124
|
base_url: str | None = None,
|
|
122
125
|
max_connections: int = 100,
|
|
@@ -151,6 +154,8 @@ class Client:
|
|
|
151
154
|
self._revocations = Revocations(revocation, self._fetch_crl)
|
|
152
155
|
self._certificate_chain = certificate_chain
|
|
153
156
|
self._check_peer = check_peer
|
|
157
|
+
self._key_log = key_log
|
|
158
|
+
self._resolve = {} if resolve is None else dict(resolve)
|
|
154
159
|
self._base_url = base_url
|
|
155
160
|
self._max_redirects = max_redirects
|
|
156
161
|
self._max_message_size = max_message_size
|
|
@@ -475,7 +480,7 @@ class Client:
|
|
|
475
480
|
else None
|
|
476
481
|
)
|
|
477
482
|
self._contexts[protocols] = ClientContext(
|
|
478
|
-
configuration, session_cache=cache
|
|
483
|
+
configuration, session_cache=cache, key_log=self._key_log
|
|
479
484
|
)
|
|
480
485
|
return self._contexts[protocols]
|
|
481
486
|
|
|
@@ -528,13 +533,19 @@ class Client:
|
|
|
528
533
|
*,
|
|
529
534
|
http1_only: bool = False,
|
|
530
535
|
) -> Connection:
|
|
536
|
+
# a resolve= entry sends a direct connection elsewhere, while the URL's host stays the name sent and verified
|
|
537
|
+
address = (
|
|
538
|
+
self._resolve.get(url.address, url.address)
|
|
539
|
+
if proxy is None
|
|
540
|
+
else url.address
|
|
541
|
+
)
|
|
531
542
|
try:
|
|
532
543
|
if quic and self._http3 is not None:
|
|
533
544
|
memory = self._memory.setdefault(url.address, OriginMemory())
|
|
534
545
|
client = await QuicClient.connect(
|
|
535
546
|
self._context((NextProtocol.H3,)),
|
|
536
547
|
url.host,
|
|
537
|
-
|
|
548
|
+
address,
|
|
538
549
|
self._http3.transport,
|
|
539
550
|
version=self._http3.quic_version,
|
|
540
551
|
proxy=proxy,
|
|
@@ -551,11 +562,12 @@ class Client:
|
|
|
551
562
|
if url.secure:
|
|
552
563
|
stream = await TLSStream.connect(
|
|
553
564
|
self._context((NextProtocol.HTTP1,) if http1_only else None),
|
|
554
|
-
|
|
565
|
+
address,
|
|
555
566
|
proxy=proxy,
|
|
556
567
|
proxy_context=self._context((NextProtocol.HTTP1,))
|
|
557
568
|
if proxy is not None and proxy.scheme is ProxyScheme.HTTPS
|
|
558
569
|
else None,
|
|
570
|
+
server_hostname=url.host,
|
|
559
571
|
)
|
|
560
572
|
await self._checked(stream, url.authority)
|
|
561
573
|
if (
|
|
@@ -587,7 +599,7 @@ class Client:
|
|
|
587
599
|
return Http1Connection(
|
|
588
600
|
transport, absolute_form=True, proxy_authorization=authorization
|
|
589
601
|
)
|
|
590
|
-
tunnel = await TLSStream.tunnel(self._context(),
|
|
602
|
+
tunnel = await TLSStream.tunnel(self._context(), address, proxy)
|
|
591
603
|
return Http1Connection(tunnel.transport, leftover=tunnel.leftover)
|
|
592
604
|
except ProxyError, ConfigurationError, RevocationError:
|
|
593
605
|
raise
|
|
@@ -687,4 +687,8 @@ class Http2StreamTransport:
|
|
|
687
687
|
async def close(self) -> None:
|
|
688
688
|
if not self._closed:
|
|
689
689
|
self._closed = True
|
|
690
|
-
|
|
690
|
+
# Client.websocket opens a connection for the tunnel alone, so the connection ends with it
|
|
691
|
+
try:
|
|
692
|
+
await self._connection.write_data(self._stream, b"", end=True)
|
|
693
|
+
finally:
|
|
694
|
+
await self._connection.close()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import secrets
|
|
3
|
-
from collections.abc import AsyncGenerator, Callable
|
|
3
|
+
from collections.abc import AsyncGenerator, Awaitable, Callable
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from enum import IntEnum
|
|
6
6
|
|
|
@@ -311,7 +311,7 @@ class Http3Connection:
|
|
|
311
311
|
return (
|
|
312
312
|
status,
|
|
313
313
|
headers,
|
|
314
|
-
Http3StreamTransport(self._quic, stream_id, deadline, frames),
|
|
314
|
+
Http3StreamTransport(self._quic, stream_id, deadline, frames, self.close),
|
|
315
315
|
)
|
|
316
316
|
|
|
317
317
|
async def close(self) -> None:
|
|
@@ -531,12 +531,18 @@ class Http3Connection:
|
|
|
531
531
|
|
|
532
532
|
class Http3StreamTransport:
|
|
533
533
|
def __init__(
|
|
534
|
-
self,
|
|
534
|
+
self,
|
|
535
|
+
quic: QuicClient,
|
|
536
|
+
stream_id: int,
|
|
537
|
+
deadline: Deadline,
|
|
538
|
+
frames: StreamFrames,
|
|
539
|
+
close_connection: Callable[[], Awaitable[None]],
|
|
535
540
|
) -> None:
|
|
536
541
|
self._quic = quic
|
|
537
542
|
self._stream_id = stream_id
|
|
538
543
|
self._frames = frames
|
|
539
544
|
self._deadline = deadline
|
|
545
|
+
self._close_connection = close_connection
|
|
540
546
|
self._pending = b""
|
|
541
547
|
self._closed = False
|
|
542
548
|
|
|
@@ -564,3 +570,5 @@ class Http3StreamTransport:
|
|
|
564
570
|
if not self._closed:
|
|
565
571
|
self._closed = True
|
|
566
572
|
self._quic.send(self._stream_id, b"", end=True)
|
|
573
|
+
# Client.websocket opens a connection for the tunnel alone, so the connection ends with it
|
|
574
|
+
await self._close_connection()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|