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.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: http_client_request
3
- Version: 0.0.6
4
- Summary: http.client request extension.
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, 6)
8
- __all__ = ["HTTPConnection", "HTTPSConnection", "HTTPResponse", "ConnectionPool", "request"]
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 socket import socket
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
- def get_host_pair(url: None | str, /) -> None | tuple[str, None | int]:
101
- if not url:
102
- return None
103
- if not url.startswith(("http://", "https://")):
104
- url = "http://" + url
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 socket.recv(sock, 1):
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.6"
4
- description = "http.client request extension."
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 = "*"