http_client_request 0.0.6__tar.gz → 0.0.8__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.
- {http_client_request-0.0.6 → http_client_request-0.0.8}/PKG-INFO +4 -3
- {http_client_request-0.0.6 → http_client_request-0.0.8}/http_client_request/__init__.py +175 -26
- {http_client_request-0.0.6 → http_client_request-0.0.8}/pyproject.toml +4 -3
- {http_client_request-0.0.6 → http_client_request-0.0.8}/http_client_request/py.typed +0 -0
- {http_client_request-0.0.6 → http_client_request-0.0.8}/readme.md +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: http_client_request
|
|
3
|
-
Version: 0.0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.0.8
|
|
4
|
+
Summary: ttp.client request extension.
|
|
5
5
|
Home-page: https://github.com/ChenyangGao/python-modules/tree/main/http_client_request
|
|
6
6
|
License: MIT
|
|
7
7
|
Keywords: http.client,request
|
|
@@ -22,10 +22,11 @@ Classifier: Topic :: Software Development :: Libraries
|
|
|
22
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
23
|
Requires-Dist: http_response (>=0.0.9)
|
|
24
24
|
Requires-Dist: python-argtools (>=0.0.2)
|
|
25
|
-
Requires-Dist: python-cookietools (>=0.1.
|
|
25
|
+
Requires-Dist: python-cookietools (>=0.1.3)
|
|
26
26
|
Requires-Dist: python-dicttools (>=0.0.4)
|
|
27
27
|
Requires-Dist: python-filewrap (>=0.2.8)
|
|
28
28
|
Requires-Dist: python-http_request (>=0.1.4)
|
|
29
|
+
Requires-Dist: python-property (>=0.0.3)
|
|
29
30
|
Requires-Dist: python-undefined (>=0.0.3)
|
|
30
31
|
Requires-Dist: urllib3
|
|
31
32
|
Requires-Dist: yarl
|
|
@@ -4,9 +4,15 @@
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
__author__ = "ChenyangGao <https://chenyanggao.github.io>"
|
|
7
|
-
__version__ = (0, 0,
|
|
8
|
-
__all__ = [
|
|
7
|
+
__version__ = (0, 0, 8)
|
|
8
|
+
__all__ = [
|
|
9
|
+
"CONNECTION_POOL", "HTTPConnection", "HTTPSConnection", "HTTPResponse",
|
|
10
|
+
"ConnectionPool", "request",
|
|
11
|
+
]
|
|
9
12
|
|
|
13
|
+
import socket
|
|
14
|
+
|
|
15
|
+
from array import array
|
|
10
16
|
from collections import defaultdict, deque, UserString
|
|
11
17
|
from collections.abc import Buffer, Callable, Iterable, Mapping
|
|
12
18
|
from http.client import (
|
|
@@ -15,9 +21,12 @@ from http.client import (
|
|
|
15
21
|
)
|
|
16
22
|
from http.cookiejar import CookieJar
|
|
17
23
|
from http.cookies import BaseCookie
|
|
24
|
+
from io import BufferedReader
|
|
18
25
|
from inspect import signature
|
|
19
26
|
from os import PathLike
|
|
20
|
-
from
|
|
27
|
+
from select import select
|
|
28
|
+
from socket import socket as Socket
|
|
29
|
+
from ssl import SSLWantReadError
|
|
21
30
|
from types import EllipsisType
|
|
22
31
|
from typing import cast, overload, Any, Final, Literal
|
|
23
32
|
from urllib.error import HTTPError
|
|
@@ -30,6 +39,7 @@ from dicttools import get_all_items
|
|
|
30
39
|
from filewrap import SupportsRead
|
|
31
40
|
from http_request import normalize_request_args, SupportsGeturl
|
|
32
41
|
from http_response import decompress_response, parse_response
|
|
42
|
+
from property import funcproperty
|
|
33
43
|
from urllib3 import HTTPResponse as Urllib3HTTPResponse, HTTPHeaderDict
|
|
34
44
|
from undefined import undefined, Undefined
|
|
35
45
|
from yarl import URL
|
|
@@ -41,15 +51,106 @@ HTTP_CONNECTION_KWARGS: Final = signature(BaseHTTPConnection).parameters.keys()
|
|
|
41
51
|
HTTPS_CONNECTION_KWARGS: Final = signature(BaseHTTPSConnection).parameters.keys()
|
|
42
52
|
|
|
43
53
|
|
|
54
|
+
def get_host_pair(url: None | str, /) -> None | tuple[str, None | int]:
|
|
55
|
+
if not url:
|
|
56
|
+
return None
|
|
57
|
+
if not url.startswith(("http://", "https://")):
|
|
58
|
+
url = "http://" + url
|
|
59
|
+
urlp = urlsplit(url)
|
|
60
|
+
return urlp.hostname or "localhost", urlp.port
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def is_ipv6(host: str, /) -> bool:
|
|
64
|
+
from ipaddress import _BaseV6, AddressValueError
|
|
65
|
+
try:
|
|
66
|
+
_BaseV6._ip_int_from_string(host) # type: ignore
|
|
67
|
+
return True
|
|
68
|
+
except AddressValueError:
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def sock_buf_readable(sock: Socket, /) -> bool:
|
|
73
|
+
rlist, *_ = select([sock], (), (), 0)
|
|
74
|
+
return bool(rlist)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
from fcntl import ioctl
|
|
79
|
+
from termios import FIONREAD
|
|
80
|
+
|
|
81
|
+
def sock_bufsize(sock, /) -> int:
|
|
82
|
+
sock_size = array("i", [0])
|
|
83
|
+
ioctl(sock, FIONREAD, sock_size)
|
|
84
|
+
return sock_size[0]
|
|
85
|
+
except ImportError:
|
|
86
|
+
from ctypes import byref, c_ulong, WinDLL # type: ignore
|
|
87
|
+
ws2_32 = WinDLL("ws2_32")
|
|
88
|
+
def sock_bufsize(sock, /) -> int:
|
|
89
|
+
FIONREAD = 0x4004667f
|
|
90
|
+
b = c_ulong(0)
|
|
91
|
+
ws2_32.ioctlsocket(sock.fileno(), FIONREAD, byref(b))
|
|
92
|
+
return b.value
|
|
93
|
+
|
|
94
|
+
|
|
44
95
|
class HTTPResponse(BaseHTTPResponse):
|
|
96
|
+
_pos: int = 0
|
|
97
|
+
method: str
|
|
45
98
|
pool: None | ConnectionPool = None
|
|
46
99
|
connection: None | HTTPConnection | HTTPSConnection = None
|
|
47
100
|
|
|
48
101
|
def __del__(self, /):
|
|
49
102
|
self.close()
|
|
50
103
|
|
|
104
|
+
@funcproperty
|
|
105
|
+
def _fp(self, /) -> BufferedReader:
|
|
106
|
+
return self.fp
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def buffer_size(self, /) -> int:
|
|
110
|
+
fp = self._fp
|
|
111
|
+
sock = fp.raw._sock
|
|
112
|
+
sock.setblocking(False)
|
|
113
|
+
try:
|
|
114
|
+
cache_size = len(fp.peek() or b"")
|
|
115
|
+
while True:
|
|
116
|
+
buffer_size = cache_size + sock_bufsize(sock)
|
|
117
|
+
if cache_size == (cache_size := len(fp.peek() or b"")):
|
|
118
|
+
return buffer_size
|
|
119
|
+
except (BlockingIOError, SSLWantReadError):
|
|
120
|
+
return 0
|
|
121
|
+
finally:
|
|
122
|
+
sock.setblocking(True)
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def unbuffer_size(self, /) -> int:
|
|
126
|
+
method = self.__dict__.get("method", "").upper()
|
|
127
|
+
content_length = self.length
|
|
128
|
+
if method == "HEAD" or content_length == 0:
|
|
129
|
+
return 0
|
|
130
|
+
if content_length:
|
|
131
|
+
return content_length - self.tell() - self.buffer_size
|
|
132
|
+
sock = self._fp.raw._sock
|
|
133
|
+
if sock_bufsize(sock) == sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF):
|
|
134
|
+
return -1
|
|
135
|
+
else:
|
|
136
|
+
return 0
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def unread_size(self, /) -> int:
|
|
140
|
+
method = self.__dict__.get("method", "").upper()
|
|
141
|
+
content_length = self.length
|
|
142
|
+
if method == "HEAD" or content_length == 0:
|
|
143
|
+
return 0
|
|
144
|
+
sock = self._fp.raw._sock
|
|
145
|
+
if content_length:
|
|
146
|
+
return content_length - self.tell()
|
|
147
|
+
elif sock_bufsize(sock) == sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF):
|
|
148
|
+
return -1
|
|
149
|
+
else:
|
|
150
|
+
return self.buffer_size
|
|
151
|
+
|
|
51
152
|
def _close_conn(self, /):
|
|
52
|
-
fp = self.fp
|
|
153
|
+
fp = self._fp = self.fp
|
|
53
154
|
setattr(self, "fp", None)
|
|
54
155
|
try:
|
|
55
156
|
pool = self.pool
|
|
@@ -70,12 +171,44 @@ class HTTPResponse(BaseHTTPResponse):
|
|
|
70
171
|
preload_content=False,
|
|
71
172
|
decode_content=True,
|
|
72
173
|
original_response=self,
|
|
73
|
-
#pool=getattr(self, "pool", None),
|
|
74
174
|
msg=self.headers,
|
|
75
175
|
request_method=getattr(self, "method", None),
|
|
76
176
|
request_url=self.url,
|
|
77
177
|
)
|
|
78
178
|
|
|
179
|
+
def read(self, /, amt=None) -> bytes:
|
|
180
|
+
data = super().read(amt)
|
|
181
|
+
self._pos += len(data)
|
|
182
|
+
return data
|
|
183
|
+
|
|
184
|
+
def read1(self, /, n=-1) -> bytes:
|
|
185
|
+
data = super().read1(n)
|
|
186
|
+
self._pos += len(data)
|
|
187
|
+
return data
|
|
188
|
+
|
|
189
|
+
def readinto(self, /, b) -> int:
|
|
190
|
+
count = super().readinto(b)
|
|
191
|
+
self._pos += count
|
|
192
|
+
return count
|
|
193
|
+
|
|
194
|
+
def readinto1(self, buffer, /) -> int:
|
|
195
|
+
count = super().readinto1(buffer)
|
|
196
|
+
self._pos += count
|
|
197
|
+
return count
|
|
198
|
+
|
|
199
|
+
def readline(self, limit=-1) -> bytes:
|
|
200
|
+
data = super().readline(limit)
|
|
201
|
+
self._pos += len(data)
|
|
202
|
+
return data
|
|
203
|
+
|
|
204
|
+
def readlines(self, hint=-1, /) -> list[bytes]:
|
|
205
|
+
ls = super().readlines(hint)
|
|
206
|
+
self._pos += sum(map(len, ls))
|
|
207
|
+
return ls
|
|
208
|
+
|
|
209
|
+
def tell(self, /) -> int:
|
|
210
|
+
return self._pos
|
|
211
|
+
|
|
79
212
|
|
|
80
213
|
class HTTPConnection(BaseHTTPConnection):
|
|
81
214
|
response_class = HTTPResponse
|
|
@@ -83,9 +216,20 @@ class HTTPConnection(BaseHTTPConnection):
|
|
|
83
216
|
def __del__(self, /):
|
|
84
217
|
self.close()
|
|
85
218
|
|
|
219
|
+
@property
|
|
220
|
+
def response(self, /) -> None | HTTPResponse:
|
|
221
|
+
return self._HTTPConnection__response # type: ignore
|
|
222
|
+
|
|
86
223
|
def getresponse(self, /) -> HTTPResponse:
|
|
87
224
|
return cast(HTTPResponse, super().getresponse())
|
|
88
225
|
|
|
226
|
+
def putrequest(self, /, *args, **kwds):
|
|
227
|
+
while True:
|
|
228
|
+
try:
|
|
229
|
+
return super().putrequest(*args, **kwds)
|
|
230
|
+
except (ConnectionResetError, BrokenPipeError):
|
|
231
|
+
self.connect()
|
|
232
|
+
|
|
89
233
|
|
|
90
234
|
class HTTPSConnection(BaseHTTPSConnection):
|
|
91
235
|
response_class = HTTPResponse
|
|
@@ -93,26 +237,19 @@ class HTTPSConnection(BaseHTTPSConnection):
|
|
|
93
237
|
def __del__(self, /):
|
|
94
238
|
self.close()
|
|
95
239
|
|
|
240
|
+
@property
|
|
241
|
+
def response(self, /) -> None | HTTPResponse:
|
|
242
|
+
return self._HTTPConnection__response # type: ignore
|
|
243
|
+
|
|
96
244
|
def getresponse(self, /) -> HTTPResponse:
|
|
97
245
|
return cast(HTTPResponse, super().getresponse())
|
|
98
246
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
urlp = urlsplit(url)
|
|
106
|
-
return urlp.hostname or "localhost", urlp.port
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def is_ipv6(host: str, /) -> bool:
|
|
110
|
-
from ipaddress import _BaseV6, AddressValueError
|
|
111
|
-
try:
|
|
112
|
-
_BaseV6._ip_int_from_string(host) # type: ignore
|
|
113
|
-
return True
|
|
114
|
-
except AddressValueError:
|
|
115
|
-
return False
|
|
247
|
+
def putrequest(self, /, *args, **kwds):
|
|
248
|
+
while True:
|
|
249
|
+
try:
|
|
250
|
+
return super().putrequest(*args, **kwds)
|
|
251
|
+
except (ConnectionResetError, BrokenPipeError):
|
|
252
|
+
self.connect()
|
|
116
253
|
|
|
117
254
|
|
|
118
255
|
class ConnectionPool:
|
|
@@ -155,21 +292,27 @@ class ConnectionPool:
|
|
|
155
292
|
con = dq.popleft()
|
|
156
293
|
except IndexError:
|
|
157
294
|
break
|
|
295
|
+
con.timeout = timeout
|
|
158
296
|
sock = con.sock
|
|
297
|
+
resp = con.response
|
|
159
298
|
if not sock or getattr(sock, "_closed"):
|
|
160
299
|
con.connect()
|
|
300
|
+
elif resp and 0 < resp.unbuffer_size <= 1024 * 1024:
|
|
301
|
+
try:
|
|
302
|
+
resp.read()
|
|
303
|
+
except (ConnectionResetError, BrokenPipeError):
|
|
304
|
+
con.connect()
|
|
161
305
|
else:
|
|
162
306
|
sock.setblocking(False)
|
|
163
307
|
try:
|
|
164
|
-
if
|
|
308
|
+
if Socket.recv(sock, 1):
|
|
165
309
|
con.connect()
|
|
166
310
|
except BlockingIOError:
|
|
167
311
|
pass
|
|
168
|
-
except ConnectionResetError:
|
|
312
|
+
except (ConnectionResetError, BrokenPipeError):
|
|
169
313
|
con.connect()
|
|
170
314
|
finally:
|
|
171
315
|
sock.setblocking(True)
|
|
172
|
-
con.timeout = timeout
|
|
173
316
|
return con
|
|
174
317
|
if url.scheme == "https":
|
|
175
318
|
return HTTPSConnection(url.hostname or "localhost", url.port, timeout=timeout)
|
|
@@ -286,7 +429,7 @@ def request[T](
|
|
|
286
429
|
raise_for_status: bool = True,
|
|
287
430
|
cookies: None | CookieJar | BaseCookie = None,
|
|
288
431
|
proxies: None | str | dict[str, str] = None,
|
|
289
|
-
pool: None | Undefined | ConnectionPool = undefined,
|
|
432
|
+
pool: None | Undefined | ConnectionPool = undefined,
|
|
290
433
|
*,
|
|
291
434
|
parse: None | EllipsisType| bool | Callable[[HTTPResponse, bytes], T] | Callable[[HTTPResponse], T] = None,
|
|
292
435
|
**request_kwargs,
|
|
@@ -350,10 +493,16 @@ def request[T](
|
|
|
350
493
|
connection = HTTPSConnection(**dict(get_all_items(request_kwargs, *HTTPS_CONNECTION_KWARGS)))
|
|
351
494
|
if http_proxy:
|
|
352
495
|
connection.set_tunnel(*http_proxy)
|
|
496
|
+
else:
|
|
497
|
+
setattr(connection, "_tunnel_host", None)
|
|
498
|
+
setattr(connection, "_tunnel_port", None)
|
|
353
499
|
else:
|
|
354
500
|
connection = HTTPConnection(**dict(get_all_items(request_kwargs, *HTTP_CONNECTION_KWARGS)))
|
|
355
501
|
if https_proxy:
|
|
356
502
|
connection.set_tunnel(*https_proxy)
|
|
503
|
+
else:
|
|
504
|
+
setattr(connection, "_tunnel_host", None)
|
|
505
|
+
setattr(connection, "_tunnel_port", None)
|
|
357
506
|
connection.request(
|
|
358
507
|
method,
|
|
359
508
|
urlunsplit(urlp._replace(scheme="", netloc="")),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "http_client_request"
|
|
3
|
-
version = "0.0.
|
|
4
|
-
description = "
|
|
3
|
+
version = "0.0.8"
|
|
4
|
+
description = "ttp.client request extension."
|
|
5
5
|
authors = ["ChenyangGao <wosiwujm@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
7
7
|
readme = "readme.md"
|
|
@@ -29,10 +29,11 @@ include = [
|
|
|
29
29
|
python = "^3.12"
|
|
30
30
|
http_response = ">=0.0.9"
|
|
31
31
|
python-argtools = ">=0.0.2"
|
|
32
|
-
python-cookietools = ">=0.1.
|
|
32
|
+
python-cookietools = ">=0.1.3"
|
|
33
33
|
python-dicttools = ">=0.0.4"
|
|
34
34
|
python-filewrap = ">=0.2.8"
|
|
35
35
|
python-http_request = ">=0.1.4"
|
|
36
|
+
python-property = ">=0.0.3"
|
|
36
37
|
python-undefined = ">=0.0.3"
|
|
37
38
|
urllib3 = "*"
|
|
38
39
|
yarl = "*"
|
|
File without changes
|
|
File without changes
|