geventhttpclient 2.3.7__tar.gz → 2.3.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.
Files changed (42) hide show
  1. {geventhttpclient-2.3.7/src/geventhttpclient.egg-info → geventhttpclient-2.3.8}/PKG-INFO +1 -1
  2. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/pyproject.toml +1 -1
  3. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/src/geventhttpclient/__init__.py +1 -1
  4. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/src/geventhttpclient/connectionpool.py +45 -8
  5. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8/src/geventhttpclient.egg-info}/PKG-INFO +1 -1
  6. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/tests/test_client.py +31 -0
  7. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/LICENSE-MIT +0 -0
  8. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/MANIFEST.in +0 -0
  9. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/README.md +0 -0
  10. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/ext/Python_compat.h +0 -0
  11. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/ext/_parser.c +0 -0
  12. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/llhttp/LICENSE-MIT +0 -0
  13. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/llhttp/include/llhttp.h +0 -0
  14. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/llhttp/src/api.c +0 -0
  15. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/llhttp/src/http.c +0 -0
  16. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/llhttp/src/llhttp.c +0 -0
  17. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/setup.cfg +0 -0
  18. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/setup.py +0 -0
  19. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/src/geventhttpclient/api.py +0 -0
  20. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/src/geventhttpclient/client.py +0 -0
  21. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/src/geventhttpclient/header.py +0 -0
  22. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/src/geventhttpclient/httplib.py +0 -0
  23. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/src/geventhttpclient/httplib2.py +0 -0
  24. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/src/geventhttpclient/requests.py +0 -0
  25. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/src/geventhttpclient/response.py +0 -0
  26. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/src/geventhttpclient/url.py +0 -0
  27. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/src/geventhttpclient/useragent.py +0 -0
  28. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/src/geventhttpclient.egg-info/SOURCES.txt +0 -0
  29. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/src/geventhttpclient.egg-info/dependency_links.txt +0 -0
  30. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/src/geventhttpclient.egg-info/requires.txt +0 -0
  31. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/src/geventhttpclient.egg-info/top_level.txt +0 -0
  32. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/tests/test_headers.py +0 -0
  33. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/tests/test_httplib.py +0 -0
  34. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/tests/test_httplib2.py +0 -0
  35. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/tests/test_keep_alive.py +0 -0
  36. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/tests/test_network_failures.py +0 -0
  37. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/tests/test_no_module_ssl.py +0 -0
  38. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/tests/test_parser.py +0 -0
  39. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/tests/test_requests.py +0 -0
  40. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/tests/test_ssl.py +0 -0
  41. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/tests/test_url.py +0 -0
  42. {geventhttpclient-2.3.7 → geventhttpclient-2.3.8}/tests/test_useragent.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: geventhttpclient
3
- Version: 2.3.7
3
+ Version: 2.3.8
4
4
  Summary: HTTP client library for gevent
5
5
  Author-email: Antonin Amand <antonin.amand@gmail.com>
6
6
  License-Expression: MIT
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "geventhttpclient"
8
- version = "2.3.7" # don't forget to update version __init__.py as well
8
+ version = "2.3.8" # 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.9"
@@ -1,6 +1,6 @@
1
1
  # package
2
2
 
3
- __version__ = "2.3.7" # don't forget to update version in pyproject.toml as well
3
+ __version__ = "2.3.8" # 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
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import socket
2
3
 
3
4
  import gevent.queue
4
5
  import gevent.socket
@@ -125,7 +126,7 @@ class ConnectionPool:
125
126
  if first_error:
126
127
  raise first_error
127
128
  else:
128
- raise RuntimeError(f"Cannot resolve {self._host}:{self._port}")
129
+ raise RuntimeError(f"Cannot resolve {self._connection_host}:{self._connection_port}")
129
130
 
130
131
  def after_connect(self, sock):
131
132
  pass
@@ -149,19 +150,55 @@ class ConnectionPool:
149
150
  if not parts or parts[1] != b"200":
150
151
  raise RuntimeError(f"Error response from Proxy server : {resp}")
151
152
 
153
+ def _is_socket_alive(self, sock):
154
+ """check if a socket is still connected and alive.
155
+
156
+ uses MSG_PEEK | MSG_DONTWAIT to check for eof without consuming data,
157
+ and without blocking.
158
+
159
+ returns false if connection is closed or broken.
160
+ """
161
+ try:
162
+ raw_sock = getattr(sock, "_sock", sock)
163
+ if raw_sock.fileno() < 0:
164
+ return False
165
+ data = raw_sock.recv(1, socket.MSG_PEEK | socket.MSG_DONTWAIT)
166
+ if data == b"":
167
+ return False
168
+ return True
169
+ except BlockingIOError:
170
+ return True
171
+ except OSError:
172
+ return False
173
+
152
174
  def get_socket(self):
153
175
  """get a socket from the pool. This blocks until one is available."""
154
176
  self._semaphore.acquire()
155
177
  if self._closed:
156
178
  raise RuntimeError("connection pool closed")
157
- try:
158
- return self._socket_queue.get(block=False)
159
- except gevent.queue.Empty:
179
+
180
+ # Try to get a valid connection from the pool
181
+ while not self._socket_queue.empty():
160
182
  try:
161
- return self._create_socket()
162
- except: # noqa
163
- self._semaphore.release()
164
- raise
183
+ sock = self._socket_queue.get(block=False)
184
+ if self._is_socket_alive(sock):
185
+ # Connection is still alive, return it
186
+ return sock
187
+ else:
188
+ # Connection is dead, close it and try next
189
+ try:
190
+ sock.close()
191
+ except: # noqa
192
+ pass
193
+ except gevent.queue.Empty:
194
+ break
195
+
196
+ # No valid connections in pool, create a new one
197
+ try:
198
+ return self._create_socket()
199
+ except: # noqa
200
+ self._semaphore.release()
201
+ raise
165
202
 
166
203
  def return_socket(self, sock):
167
204
  """return a socket to the pool."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: geventhttpclient
3
- Version: 2.3.7
3
+ Version: 2.3.8
4
4
  Summary: HTTP client library for gevent
5
5
  Author-email: Antonin Amand <antonin.amand@gmail.com>
6
6
  License-Expression: MIT
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import socket
2
3
 
3
4
  import gevent.pool
4
5
  import gevent.queue
@@ -7,6 +8,7 @@ import pytest
7
8
 
8
9
  from geventhttpclient import __version__
9
10
  from geventhttpclient.client import METHOD_GET, HTTPClient
11
+ from geventhttpclient.connectionpool import ConnectionPool
10
12
  from tests.common import HTTPBIN_HOST, LISTENER, check_upload, server, wsgiserver
11
13
 
12
14
 
@@ -350,3 +352,32 @@ def test_multi_queries_greenlet_safe():
350
352
  assert count == 5
351
353
  # ensure at least 3 of requests got 200
352
354
  assert ok_count >= 3
355
+
356
+
357
+ class TestIsSocketAlive:
358
+ """Unit tests for ConnectionPool._is_socket_alive()"""
359
+
360
+ def test_closed_socket_returns_false(self):
361
+ sock = socket.socket()
362
+ sock.close()
363
+ pool = ConnectionPool("127.0.0.1", 80, "127.0.0.1", 80)
364
+ assert sock.fileno() < 0
365
+ assert pool._is_socket_alive(sock) is False
366
+
367
+ def test_healthy_socket_returns_true(self):
368
+ s1, s2 = socket.socketpair()
369
+ try:
370
+ pool = ConnectionPool("127.0.0.1", 80, "127.0.0.1", 80)
371
+ assert pool._is_socket_alive(s1) is True
372
+ finally:
373
+ s1.close()
374
+ s2.close()
375
+
376
+ def test_peer_orderly_close_returns_false(self):
377
+ s1, s2 = socket.socketpair()
378
+ try:
379
+ s2.close()
380
+ pool = ConnectionPool("127.0.0.1", 80, "127.0.0.1", 80)
381
+ assert pool._is_socket_alive(s1) is False
382
+ finally:
383
+ s1.close()