http_client_request 0.0.7__tar.gz → 0.0.9__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.7
4
- Summary: ttp.client request extension.
3
+ Version: 0.0.9
4
+ Summary: http.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,10 @@ 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.2)
25
+ Requires-Dist: python-cookietools (>=0.1.4)
26
26
  Requires-Dist: python-dicttools (>=0.0.4)
27
- Requires-Dist: python-filewrap (>=0.2.8)
28
- Requires-Dist: python-http_request (>=0.1.4)
27
+ Requires-Dist: python-filewrap (>=0.2.9)
28
+ Requires-Dist: python-http_request (>=0.1.6)
29
29
  Requires-Dist: python-property (>=0.0.3)
30
30
  Requires-Dist: python-undefined (>=0.0.3)
31
31
  Requires-Dist: urllib3
@@ -4,7 +4,7 @@
4
4
  from __future__ import annotations
5
5
 
6
6
  __author__ = "ChenyangGao <https://chenyanggao.github.io>"
7
- __version__ = (0, 0, 7)
7
+ __version__ = (0, 0, 9)
8
8
  __all__ = [
9
9
  "CONNECTION_POOL", "HTTPConnection", "HTTPSConnection", "HTTPResponse",
10
10
  "ConnectionPool", "request",
@@ -24,8 +24,10 @@ from http.cookies import BaseCookie
24
24
  from io import BufferedReader
25
25
  from inspect import signature
26
26
  from os import PathLike
27
+ from platform import system
27
28
  from select import select
28
29
  from socket import socket as Socket
30
+ from ssl import SSLWantReadError
29
31
  from types import EllipsisType
30
32
  from typing import cast, overload, Any, Final, Literal
31
33
  from urllib.error import HTTPError
@@ -73,6 +75,68 @@ def sock_buf_readable(sock: Socket, /) -> bool:
73
75
  return bool(rlist)
74
76
 
75
77
 
78
+ def set_keepalive_linux(
79
+ sock: Socket,
80
+ after_idle_sec: int = 1,
81
+ interval_sec: int = 5,
82
+ max_fails: int = 5,
83
+ ):
84
+ """Set TCP keepalive on an open socket on Linux.
85
+ """
86
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
87
+ sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, after_idle_sec) # type: ignore
88
+ sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, interval_sec)
89
+ sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, max_fails)
90
+
91
+
92
+ def set_keepalive_osx(
93
+ sock: Socket,
94
+ after_idle_sec: int = 1,
95
+ interval_sec: int = 5,
96
+ max_fails: int = 5,
97
+ ):
98
+ """Set TCP keepalive on an open socket on MaxOSX.
99
+ """
100
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
101
+ sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPALIVE, after_idle_sec) # type: ignore
102
+ sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, interval_sec)
103
+ sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, max_fails)
104
+
105
+
106
+ def set_keepalive_win(
107
+ sock: Socket,
108
+ after_idle_sec: int = 1,
109
+ interval_sec: int = 5,
110
+ max_fails: int = 5,
111
+ ):
112
+ """Set TCP keepalive on an open socket on Windows.
113
+ """
114
+ sock.ioctl(socket.SIO_KEEPALIVE_VALS, (1, after_idle_sec * 1000, interval_sec * 1000)) # type: ignore
115
+
116
+
117
+ def set_keepalive(
118
+ sock: Socket,
119
+ after_idle_sec: int = 1,
120
+ interval_sec: int = 3,
121
+ max_fails: int = 5,
122
+ ):
123
+ """Set TCP keepalive on an open socket on multiple platforms.
124
+
125
+ It activates after `after_idle_sec` second of idleness,
126
+ then sends a keepalive ping once every `interval_sec` seconds,
127
+ and closes the connection after `max_fails` failed ping (max_fails), or 15 seconds.
128
+ """
129
+ platform = system()
130
+ match platform:
131
+ case "Darwin":
132
+ return set_keepalive_osx(sock, after_idle_sec, interval_sec, max_fails)
133
+ case "Windows":
134
+ return set_keepalive_win(sock, after_idle_sec, interval_sec, max_fails)
135
+ case "Linux":
136
+ return set_keepalive_linux(sock, after_idle_sec, interval_sec, max_fails)
137
+ raise RuntimeError(f"unsupport platform {platform!r}")
138
+
139
+
76
140
  try:
77
141
  from fcntl import ioctl
78
142
  from termios import FIONREAD
@@ -108,11 +172,17 @@ class HTTPResponse(BaseHTTPResponse):
108
172
  def buffer_size(self, /) -> int:
109
173
  fp = self._fp
110
174
  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
175
+ sock.setblocking(False)
176
+ try:
177
+ cache_size = len(fp.peek() or b"")
178
+ while True:
179
+ buffer_size = cache_size + sock_bufsize(sock)
180
+ if cache_size == (cache_size := len(fp.peek() or b"")):
181
+ return buffer_size
182
+ except (BlockingIOError, SSLWantReadError):
183
+ return 0
184
+ finally:
185
+ sock.setblocking(True)
116
186
 
117
187
  @property
118
188
  def unbuffer_size(self, /) -> int:
@@ -211,7 +281,11 @@ class HTTPConnection(BaseHTTPConnection):
211
281
 
212
282
  @property
213
283
  def response(self, /) -> None | HTTPResponse:
214
- return self.__response # type: ignore
284
+ return self._HTTPConnection__response # type: ignore
285
+
286
+ def connect(self, /):
287
+ super().connect()
288
+ set_keepalive(self.sock)
215
289
 
216
290
  def getresponse(self, /) -> HTTPResponse:
217
291
  return cast(HTTPResponse, super().getresponse())
@@ -232,7 +306,11 @@ class HTTPSConnection(BaseHTTPSConnection):
232
306
 
233
307
  @property
234
308
  def response(self, /) -> None | HTTPResponse:
235
- return self.__response # type: ignore
309
+ return self._HTTPConnection__response # type: ignore
310
+
311
+ def connect(self, /):
312
+ super().connect()
313
+ set_keepalive(self.sock)
236
314
 
237
315
  def getresponse(self, /) -> HTTPResponse:
238
316
  return cast(HTTPResponse, super().getresponse())
@@ -290,12 +368,11 @@ class ConnectionPool:
290
368
  resp = con.response
291
369
  if not sock or getattr(sock, "_closed"):
292
370
  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()
371
+ elif resp and 0 < resp.unbuffer_size <= 1024 * 1024:
372
+ try:
373
+ resp.read()
374
+ except (ConnectionResetError, BrokenPipeError):
375
+ con.connect()
299
376
  else:
300
377
  sock.setblocking(False)
301
378
  try:
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
2
  name = "http_client_request"
3
- version = "0.0.7"
4
- description = "ttp.client request extension."
3
+ version = "0.0.9"
4
+ description = "http.client request extension."
5
5
  authors = ["ChenyangGao <wosiwujm@gmail.com>"]
6
6
  license = "MIT"
7
7
  readme = "readme.md"
@@ -29,10 +29,10 @@ 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.2"
32
+ python-cookietools = ">=0.1.4"
33
33
  python-dicttools = ">=0.0.4"
34
- python-filewrap = ">=0.2.8"
35
- python-http_request = ">=0.1.4"
34
+ python-filewrap = ">=0.2.9"
35
+ python-http_request = ">=0.1.6"
36
36
  python-property = ">=0.0.3"
37
37
  python-undefined = ">=0.0.3"
38
38
  urllib3 = "*"