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