geventhttpclient 2.5.0__tar.gz → 2.5.1__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.
- {geventhttpclient-2.5.0/src/geventhttpclient.egg-info → geventhttpclient-2.5.1}/PKG-INFO +1 -1
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/pyproject.toml +1 -1
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/src/geventhttpclient/__init__.py +1 -1
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/src/geventhttpclient/client.py +10 -3
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1/src/geventhttpclient.egg-info}/PKG-INFO +1 -1
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/tests/test_client.py +55 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/LICENSE-MIT +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/MANIFEST.in +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/README.md +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/ext/Python_compat.h +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/ext/_parser.c +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/llhttp/LICENSE-MIT +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/llhttp/include/llhttp.h +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/llhttp/src/api.c +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/llhttp/src/http.c +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/llhttp/src/llhttp.c +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/setup.cfg +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/setup.py +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/src/geventhttpclient/api.py +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/src/geventhttpclient/connectionpool.py +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/src/geventhttpclient/header.py +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/src/geventhttpclient/httplib.py +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/src/geventhttpclient/httplib2.py +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/src/geventhttpclient/requests.py +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/src/geventhttpclient/response.py +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/src/geventhttpclient/url.py +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/src/geventhttpclient/useragent.py +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/src/geventhttpclient.egg-info/SOURCES.txt +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/src/geventhttpclient.egg-info/dependency_links.txt +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/src/geventhttpclient.egg-info/requires.txt +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/src/geventhttpclient.egg-info/top_level.txt +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/tests/test_continue.py +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/tests/test_headers.py +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/tests/test_httplib.py +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/tests/test_keep_alive.py +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/tests/test_network_failures.py +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/tests/test_parser.py +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/tests/test_proxy.py +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/tests/test_requests.py +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/tests/test_ssl.py +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/tests/test_url.py +0 -0
- {geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/tests/test_useragent.py +0 -0
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "geventhttpclient"
|
|
8
|
-
version = "2.5.
|
|
8
|
+
version = "2.5.1" # don't forget to update version __init__.py as well
|
|
9
9
|
description = "HTTP client library for gevent"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.11"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# package
|
|
2
2
|
|
|
3
|
-
__version__ = "2.5.
|
|
3
|
+
__version__ = "2.5.1" # don't forget to update version in pyproject.toml as well
|
|
4
4
|
|
|
5
5
|
from geventhttpclient.api import delete, get, head, options, patch, post, put, request
|
|
6
6
|
from geventhttpclient.client import HTTPClient
|
|
@@ -72,6 +72,9 @@ def _uses_chunked_transfer(header_fields: Mapping[str, Any], body: Any) -> bool:
|
|
|
72
72
|
and TRANSFER_ENCODING_CHUNKED in str(value).lower()
|
|
73
73
|
):
|
|
74
74
|
return True
|
|
75
|
+
if body is None:
|
|
76
|
+
# No body at all; unknown-length streams are chunked, None is not.
|
|
77
|
+
return False
|
|
75
78
|
return not isinstance(body, (bytes, bytearray, memoryview)) and _get_body_length(body) is None
|
|
76
79
|
|
|
77
80
|
|
|
@@ -348,7 +351,10 @@ class HTTPClient:
|
|
|
348
351
|
:return:
|
|
349
352
|
"""
|
|
350
353
|
|
|
351
|
-
if
|
|
354
|
+
if body is None:
|
|
355
|
+
# None means "no body", not a body of unknown length.
|
|
356
|
+
body = b""
|
|
357
|
+
elif isinstance(body, str):
|
|
352
358
|
body = body.encode("utf-8")
|
|
353
359
|
|
|
354
360
|
# the same case-insensitive merge `_build_request` performs before
|
|
@@ -425,15 +431,16 @@ class HTTPClient:
|
|
|
425
431
|
response._sent_request = request
|
|
426
432
|
return response
|
|
427
433
|
if chunked:
|
|
428
|
-
sock.sendall(_request)
|
|
429
434
|
# Note: on retry, file-like/iterable bodies continue
|
|
430
435
|
# from their current position or are exhausted, same
|
|
431
436
|
# as with `sendfile` before.
|
|
432
437
|
if body:
|
|
438
|
+
sock.sendall(_request)
|
|
433
439
|
for block in _iter_chunked(body, self.block_size):
|
|
434
440
|
sock.sendall(block)
|
|
435
441
|
else:
|
|
436
|
-
|
|
442
|
+
# Single write: a separate small write stalls on Nagle.
|
|
443
|
+
sock.sendall(_request + b"0\r\n\r\n")
|
|
437
444
|
elif body:
|
|
438
445
|
if isinstance(body, bytes):
|
|
439
446
|
sock.sendall(_request + body)
|
|
@@ -6,6 +6,7 @@ import sys
|
|
|
6
6
|
import gevent.pool
|
|
7
7
|
import gevent.queue
|
|
8
8
|
import gevent.server
|
|
9
|
+
import gevent.socket
|
|
9
10
|
import pytest
|
|
10
11
|
|
|
11
12
|
from geventhttpclient import __version__
|
|
@@ -239,6 +240,60 @@ def test_post_chunked_request_empty_body_with_header():
|
|
|
239
240
|
assert response.status_code == 200
|
|
240
241
|
|
|
241
242
|
|
|
243
|
+
def test_request_body_none_is_not_chunked():
|
|
244
|
+
"""Regression: body=None means "no body", not an unknown-length body (#248)."""
|
|
245
|
+
|
|
246
|
+
def handler(sock, addr):
|
|
247
|
+
# The request is complete after the headers; chunked would add `0\r\n\r\n`.
|
|
248
|
+
data = b""
|
|
249
|
+
while b"\r\n\r\n" not in data:
|
|
250
|
+
block = sock.recv(4096)
|
|
251
|
+
assert block, "connection closed before request was complete"
|
|
252
|
+
data += block
|
|
253
|
+
header, _, rest = data.partition(b"\r\n\r\n")
|
|
254
|
+
assert header.lower().startswith(b"get / http/1.1")
|
|
255
|
+
assert b"transfer-encoding" not in header.lower()
|
|
256
|
+
assert b"content-length" not in header.lower()
|
|
257
|
+
assert rest == b"", f"unexpected body bytes after headers: {rest!r}"
|
|
258
|
+
sock.sendall(b"HTTP/1.1 200 Ok\r\nContent-Length: 0\r\nConnection: close\r\n\r\n")
|
|
259
|
+
|
|
260
|
+
with server(handler):
|
|
261
|
+
client = HTTPClient(*LISTENER)
|
|
262
|
+
response = client.request(METHOD_GET, "/", body=None)
|
|
263
|
+
assert response.status_code == 200
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def test_chunked_empty_body_single_write(monkeypatch):
|
|
267
|
+
"""An explicit chunked request without body must go out in a single write
|
|
268
|
+
(a second small write stalls on Nagle until the delayed ACK)."""
|
|
269
|
+
|
|
270
|
+
def handler(sock, addr):
|
|
271
|
+
data = b""
|
|
272
|
+
while not data.endswith(b"0\r\n\r\n"):
|
|
273
|
+
block = sock.recv(4096)
|
|
274
|
+
assert block, "connection closed before final chunk"
|
|
275
|
+
data += block
|
|
276
|
+
sock.sendall(b"HTTP/1.1 200 Ok\r\nContent-Length: 0\r\nConnection: close\r\n\r\n")
|
|
277
|
+
|
|
278
|
+
client_writes = []
|
|
279
|
+
original_sendall = gevent.socket.socket.sendall
|
|
280
|
+
|
|
281
|
+
def recording_sendall(sock, data):
|
|
282
|
+
if data.startswith(b"POST"):
|
|
283
|
+
client_writes.append(bytes(data))
|
|
284
|
+
return original_sendall(sock, data)
|
|
285
|
+
|
|
286
|
+
monkeypatch.setattr(gevent.socket.socket, "sendall", recording_sendall)
|
|
287
|
+
with server(handler):
|
|
288
|
+
client = HTTPClient(*LISTENER)
|
|
289
|
+
response = client.post("/", body=b"", headers={"Transfer-Encoding": "chunked"})
|
|
290
|
+
assert response.status_code == 200
|
|
291
|
+
|
|
292
|
+
assert len(client_writes) == 1
|
|
293
|
+
assert client_writes[0].startswith(b"POST / HTTP/1.1\r\n")
|
|
294
|
+
assert client_writes[0].endswith(b"\r\n\r\n0\r\n\r\n")
|
|
295
|
+
|
|
296
|
+
|
|
242
297
|
def test_chunked_request_rejects_http_1_0():
|
|
243
298
|
client = HTTPClient(*LISTENER, version=HTTPClient.HTTP_10)
|
|
244
299
|
with pytest.raises(ValueError):
|
|
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
|
{geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/src/geventhttpclient.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/src/geventhttpclient.egg-info/requires.txt
RENAMED
|
File without changes
|
{geventhttpclient-2.5.0 → geventhttpclient-2.5.1}/src/geventhttpclient.egg-info/top_level.txt
RENAMED
|
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
|