Skip to content

Commit d83ab6b

Browse files
authored
🔖 Release 3.7.0 (#132)
**Added** - TransferProgress tracking in Response when downloading using `stream=True` based on the Content-Length. (#127) There's no easy way to track the "real" amount of bytes consumed using "iter_content" when the remote is sending a compressed body. This change makes it possible to track the amount of bytes consumed. The `Response` object now contain a property named `download_progress` that is either `None` or a `TransferProgress` object. - HTTP/2 with prior knowledge over TLS or via an unencrypted connection. `disable_http1` toggle is now available through your `Session` constructor. In consequence, you may leverage all HTTP/2 capabilities like multiplexing using a plain (e.g. non-TLS) socket. You may enable/disable any protocols per Session object (but not all of them at once!). In non-TLS connections, you have to keep one of HTTP/1.1 or HTTP/2 enabled. Otherwise, one of HTTP/1.1, HTTP/2 or HTTP/3. A `RuntimeError` may be thrown if no protocol can be used in a given context. **Changed** - Relax main API constraint in get, head, options and delete methods / functions by accepting kwargs. - urllib3-future lower bound version is raised to 2.8.900
1 parent e43242a commit d83ab6b

10 files changed

+187
-7
lines changed

HISTORY.md

+20
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
11
Release History
22
===============
33

4+
3.7.0 (2024-06-24)
5+
------------------
6+
7+
**Added**
8+
- TransferProgress tracking in Response when downloading using `stream=True` based on the Content-Length. (#127)
9+
There's no easy way to track the "real" amount of bytes consumed using "iter_content" when the remote is
10+
sending a compressed body. This change makes it possible to track the amount of bytes consumed.
11+
The `Response` object now contain a property named `download_progress` that is either `None` or a `TransferProgress` object.
12+
- HTTP/2 with prior knowledge over TLS or via an unencrypted connection.
13+
`disable_http1` toggle is now available through your `Session` constructor.
14+
In consequence, you may leverage all HTTP/2 capabilities like multiplexing using a plain (e.g. non-TLS) socket.
15+
You may enable/disable any protocols per Session object (but not all of them at once!).
16+
In non-TLS connections, you have to keep one of HTTP/1.1 or HTTP/2 enabled.
17+
Otherwise, one of HTTP/1.1, HTTP/2 or HTTP/3. A `RuntimeError` may be thrown if no protocol can be used in a
18+
given context.
19+
20+
**Changed**
21+
- Relax main API constraint in get, head, options and delete methods / functions by accepting kwargs.
22+
- urllib3-future lower bound version is raised to 2.8.900
23+
424
3.6.7 (2024-06-19)
525
------------------
626

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Niquests, is the “**Safest**, **Fastest[^10]**, **Easiest**, and **Most advanc
3939
| `Direct HTTP/3 Negotiation` |[^9] | N/A[^8] | N/A[^8] | N/A[^8] |
4040
| `Happy Eyeballs` |||||
4141
| `Package / SLSA Signed` |||||
42+
| `HTTP/2 with prior knowledge (h2c)` |||||
4243
</details>
4344

4445
<details>
@@ -144,6 +145,7 @@ Niquests is ready for the demands of building scalable, robust and reliable HTTP
144145
- Basic & Digest Authentication
145146
- Familiar `dict`–like Cookies
146147
- Network settings fine-tuning
148+
- HTTP/2 with prior knowledge
147149
- Object-oriented headers
148150
- Multi-part File Uploads
149151
- Chunked HTTP Requests

docs/user/advanced.rst

+56-3
Original file line numberDiff line numberDiff line change
@@ -1299,21 +1299,37 @@ by passing a custom ``QuicSharedCache`` instance like so::
12991299

13001300
When the cache is full, the oldest entry is removed.
13011301

1302-
Disable HTTP/2, and/or HTTP/3
1302+
Disable HTTP/1.1, HTTP/2, and/or HTTP/3
13031303
-----------------------------
13041304

13051305
You can at your own discretion disable a protocol by passing ``disable_http2=True`` or
13061306
``disable_http3=True`` within your ``Session`` constructor.
13071307

1308-
.. warning:: It is actually forbidden to disable HTTP/1.1 as the underlying library (urllib3.future) does not permit it for now.
1309-
13101308
Having a session without HTTP/2 enabled should be done that way::
13111309

13121310
import niquests
13131311

13141312
session = niquests.Session(disable_http2=True)
13151313

13161314

1315+
HTTP/2 with prior knowledge
1316+
---------------------------
1317+
1318+
Interacting with a server over plain text using the HTTP/2 protocol must be done by
1319+
disabling HTTP/1.1 entirely, so that Niquests knows that you know in advance what the remote is capable of.
1320+
1321+
Following this example::
1322+
1323+
import niquests
1324+
1325+
session = niquests.Session(disable_http1=True)
1326+
r = session.get("http://my-special-svc.local")
1327+
r.version # 20 (aka. HTTP/2)
1328+
1329+
.. note:: You may do the same for servers that do not support the ALPN extension for https URLs.
1330+
1331+
.. warning:: Disabling HTTP/1.1 and HTTP/2 will raise an error (RuntimeError) for non https URLs! As HTTP/3 is designed for the QUIC layer, which itself is based on TLS 1.3.
1332+
13171333
Thread Safety
13181334
-------------
13191335

@@ -1417,3 +1433,40 @@ Here is a simple example::
14171433
session.get("https://pie.dev/get", verify="sha256_8fff956b66667ffe5801c8432b12c367254727782d91bc695b7a53d0b512d721")
14181434

14191435
.. warning:: Supported fingerprinting algorithms are sha256, and sha1. The prefix is mandatory.
1436+
1437+
TLS Fingerprint (like JA3)
1438+
--------------------------
1439+
1440+
Some of you seems to be interested in that topic, at least according to the statistics presented to me.
1441+
Niquests is dedicated to providing a software that present a unique and close enough signature (against modern browser)
1442+
that you should be protected against TLS censorship / blocking technics.
1443+
1444+
We are actively working toward a way to permanently improving this.
1445+
To help us fighting toward the greater good, feel free to sponsor us and/or speaking out loud about your
1446+
experiences, whether about a specific country practices or global ISP/Cloud provider ones.
1447+
1448+
.. note:: If you are getting blocked, come and get in touch with us through our Github issues.
1449+
1450+
Tracking the real download speed
1451+
--------------------------------
1452+
1453+
In a rare case, you may be left with no clue on what is the real "download speed" due to the
1454+
remote server applying a "transfer-encoding" or also know as compressing (zstd, br or gzip).
1455+
1456+
Niquests automatically decompress response bodies, so doing a call to ``iter_content`` is not going to yield
1457+
the size actually extracted from the socket but rather from the decompressor algorithm.
1458+
1459+
To remediate this issue we've implemented a new property into your ``Response`` object. Named ``download_progress``
1460+
that is a ``TransferProgress`` instance.
1461+
1462+
.. warning:: This feature is enabled when ``stream=True``.
1463+
1464+
Here is a basic example of how you would proceed::
1465+
1466+
import niquests
1467+
1468+
with niquests.Session() as s:
1469+
with s.get("https://ash-speed.hetzner.com/100MB.bin", stream=True) as r:
1470+
for chunk in r.iter_content():
1471+
# do anything you want with chunk
1472+
print(r.download_progress.total) # this actually contain the amt of bytes (raw) downloaded from the socket.

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ dynamic = ["version"]
4141
dependencies = [
4242
"charset_normalizer>=2,<4",
4343
"idna>=2.5,<4",
44-
"urllib3.future>=2.7.905,<3",
44+
"urllib3.future>=2.8.900,<3",
4545
"wassima>=1.0.1,<2",
4646
"kiss_headers>=2,<4",
4747
]

src/niquests/__version__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
__url__: str = "https://niquests.readthedocs.io"
1010

1111
__version__: str
12-
__version__ = "3.6.7"
12+
__version__ = "3.7.0"
1313

14-
__build__: int = 0x030607
14+
__build__: int = 0x030700
1515
__author__: str = "Kenneth Reitz"
1616
__author_email__: str = "me@kennethreitz.org"
1717
__license__: str = "Apache-2.0"

src/niquests/_async.py

+18
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ def __init__(
117117
quic_cache_layer: CacheLayerAltSvcType | None = None,
118118
retries: RetryType = DEFAULT_RETRIES,
119119
multiplexed: bool = False,
120+
disable_http1: bool = False,
120121
disable_http2: bool = False,
121122
disable_http3: bool = False,
122123
disable_ipv6: bool = False,
@@ -177,6 +178,7 @@ def __init__(
177178
#: Bind to address/network adapter
178179
self.source_address = source_address
179180

181+
self._disable_http1 = disable_http1
180182
self._disable_http2 = disable_http2
181183
self._disable_http3 = disable_http3
182184

@@ -797,6 +799,7 @@ async def get(
797799
verify: TLSVerifyType = ...,
798800
stream: Literal[False] = ...,
799801
cert: TLSClientCertType | None = ...,
802+
**kwargs: typing.Any,
800803
) -> Response: ...
801804

802805
@typing.overload # type: ignore[override]
@@ -815,6 +818,7 @@ async def get(
815818
verify: TLSVerifyType = ...,
816819
stream: Literal[True],
817820
cert: TLSClientCertType | None = ...,
821+
**kwargs: typing.Any,
818822
) -> AsyncResponse: ...
819823

820824
async def get( # type: ignore[override]
@@ -832,6 +836,7 @@ async def get( # type: ignore[override]
832836
verify: TLSVerifyType = True,
833837
stream: bool = False,
834838
cert: TLSClientCertType | None = None,
839+
**kwargs: typing.Any,
835840
) -> Response | AsyncResponse:
836841
return await self.request( # type: ignore[call-overload,misc]
837842
"GET",
@@ -847,6 +852,7 @@ async def get( # type: ignore[override]
847852
verify=verify,
848853
stream=stream,
849854
cert=cert,
855+
**kwargs,
850856
)
851857

852858
@typing.overload # type: ignore[override]
@@ -865,6 +871,7 @@ async def options(
865871
verify: TLSVerifyType = ...,
866872
stream: Literal[False] = ...,
867873
cert: TLSClientCertType | None = ...,
874+
**kwargs: typing.Any,
868875
) -> Response: ...
869876

870877
@typing.overload # type: ignore[override]
@@ -883,6 +890,7 @@ async def options(
883890
verify: TLSVerifyType = ...,
884891
stream: Literal[True],
885892
cert: TLSClientCertType | None = ...,
893+
**kwargs: typing.Any,
886894
) -> AsyncResponse: ...
887895

888896
async def options( # type: ignore[override]
@@ -900,6 +908,7 @@ async def options( # type: ignore[override]
900908
verify: TLSVerifyType = True,
901909
stream: bool = False,
902910
cert: TLSClientCertType | None = None,
911+
**kwargs: typing.Any,
903912
) -> Response | AsyncResponse:
904913
return await self.request( # type: ignore[call-overload,misc]
905914
"OPTIONS",
@@ -915,6 +924,7 @@ async def options( # type: ignore[override]
915924
verify=verify,
916925
stream=stream,
917926
cert=cert,
927+
**kwargs,
918928
)
919929

920930
@typing.overload # type: ignore[override]
@@ -933,6 +943,7 @@ async def head(
933943
verify: TLSVerifyType = ...,
934944
stream: Literal[False] = ...,
935945
cert: TLSClientCertType | None = ...,
946+
**kwargs: typing.Any,
936947
) -> Response: ...
937948

938949
@typing.overload # type: ignore[override]
@@ -951,6 +962,7 @@ async def head(
951962
verify: TLSVerifyType = ...,
952963
stream: Literal[True],
953964
cert: TLSClientCertType | None = ...,
965+
**kwargs: typing.Any,
954966
) -> AsyncResponse: ...
955967

956968
async def head( # type: ignore[override]
@@ -968,6 +980,7 @@ async def head( # type: ignore[override]
968980
verify: TLSVerifyType = True,
969981
stream: bool = False,
970982
cert: TLSClientCertType | None = None,
983+
**kwargs: typing.Any,
971984
) -> Response | AsyncResponse:
972985
return await self.request( # type: ignore[call-overload,misc]
973986
"HEAD",
@@ -983,6 +996,7 @@ async def head( # type: ignore[override]
983996
verify=verify,
984997
stream=stream,
985998
cert=cert,
999+
**kwargs,
9861000
)
9871001

9881002
@typing.overload # type: ignore[override]
@@ -1241,6 +1255,7 @@ async def delete(
12411255
verify: TLSVerifyType = ...,
12421256
stream: Literal[False] = ...,
12431257
cert: TLSClientCertType | None = ...,
1258+
**kwargs: typing.Any,
12441259
) -> Response: ...
12451260

12461261
@typing.overload # type: ignore[override]
@@ -1259,6 +1274,7 @@ async def delete(
12591274
verify: TLSVerifyType = ...,
12601275
stream: Literal[True],
12611276
cert: TLSClientCertType | None = ...,
1277+
**kwargs: typing.Any,
12621278
) -> AsyncResponse: ...
12631279

12641280
async def delete( # type: ignore[override]
@@ -1276,6 +1292,7 @@ async def delete( # type: ignore[override]
12761292
verify: TLSVerifyType = True,
12771293
stream: bool = False,
12781294
cert: TLSClientCertType | None = None,
1295+
**kwargs: typing.Any,
12791296
) -> Response | AsyncResponse:
12801297
return await self.request( # type: ignore[call-overload,misc]
12811298
"DELETE",
@@ -1291,6 +1308,7 @@ async def delete( # type: ignore[override]
12911308
verify=verify,
12921309
stream=stream,
12931310
cert=cert,
1311+
**kwargs,
12941312
)
12951313

12961314
async def gather(self, *responses: Response, max_fetch: int | None = None) -> None: # type: ignore[override]

src/niquests/adapters.py

+16
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ class HTTPAdapter(BaseAdapter):
313313
"_pool_maxsize",
314314
"_pool_block",
315315
"_quic_cache_layer",
316+
"_disable_http1",
316317
"_disable_http2",
317318
"_disable_http3",
318319
"_source_address",
@@ -327,7 +328,9 @@ def __init__(
327328
pool_maxsize: int = DEFAULT_POOLSIZE,
328329
max_retries: RetryType = DEFAULT_RETRIES,
329330
pool_block: bool = DEFAULT_POOLBLOCK,
331+
*, # todo: revert if any complaint about it... :s
330332
quic_cache_layer: CacheLayerAltSvcType | None = None,
333+
disable_http1: bool = False,
331334
disable_http2: bool = False,
332335
disable_http3: bool = False,
333336
max_in_flight_multiplexed: int | None = None,
@@ -359,6 +362,7 @@ def __init__(
359362
self._pool_maxsize = pool_maxsize
360363
self._pool_block = pool_block
361364
self._quic_cache_layer = quic_cache_layer
365+
self._disable_http1 = disable_http1
362366
self._disable_http2 = disable_http2
363367
self._disable_http3 = disable_http3
364368
self._resolver = resolver
@@ -379,6 +383,8 @@ def __init__(
379383

380384
disabled_svn = set()
381385

386+
if disable_http1:
387+
disabled_svn.add(HttpVersion.h11)
382388
if disable_http2:
383389
disabled_svn.add(HttpVersion.h2)
384390
if disable_http3:
@@ -412,6 +418,8 @@ def __setstate__(self, state):
412418

413419
disabled_svn = set()
414420

421+
if self._disable_http1:
422+
disabled_svn.add(HttpVersion.h11)
415423
if self._disable_http2:
416424
disabled_svn.add(HttpVersion.h2)
417425
if self._disable_http3:
@@ -1284,6 +1292,7 @@ class AsyncHTTPAdapter(AsyncBaseAdapter):
12841292
"_pool_maxsize",
12851293
"_pool_block",
12861294
"_quic_cache_layer",
1295+
"_disable_http1",
12871296
"_disable_http2",
12881297
"_disable_http3",
12891298
"_source_address",
@@ -1298,7 +1307,9 @@ def __init__(
12981307
pool_maxsize: int = DEFAULT_POOLSIZE,
12991308
max_retries: RetryType = DEFAULT_RETRIES,
13001309
pool_block: bool = DEFAULT_POOLBLOCK,
1310+
*,
13011311
quic_cache_layer: CacheLayerAltSvcType | None = None,
1312+
disable_http1: bool = False,
13021313
disable_http2: bool = False,
13031314
disable_http3: bool = False,
13041315
max_in_flight_multiplexed: int | None = None,
@@ -1331,6 +1342,7 @@ def __init__(
13311342
self._pool_maxsize = pool_maxsize
13321343
self._pool_block = pool_block
13331344
self._quic_cache_layer = quic_cache_layer
1345+
self._disable_http1 = disable_http1
13341346
self._disable_http2 = disable_http2
13351347
self._disable_http3 = disable_http3
13361348
self._resolver = resolver
@@ -1350,6 +1362,8 @@ def __init__(
13501362

13511363
disabled_svn = set()
13521364

1365+
if disable_http1:
1366+
disabled_svn.add(HttpVersion.h11)
13531367
if disable_http2:
13541368
disabled_svn.add(HttpVersion.h2)
13551369
if disable_http3:
@@ -1383,6 +1397,8 @@ def __setstate__(self, state):
13831397

13841398
disabled_svn = set()
13851399

1400+
if self._disable_http1:
1401+
disabled_svn.add(HttpVersion.h11)
13861402
if self._disable_http2:
13871403
disabled_svn.add(HttpVersion.h2)
13881404
if self._disable_http3:

0 commit comments

Comments
 (0)