geventhttpclient 2.2.0__tar.gz → 2.2.1__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 (49) hide show
  1. geventhttpclient-2.2.1/LICENSE-MIT +22 -0
  2. {geventhttpclient-2.2.0/src/geventhttpclient.egg-info → geventhttpclient-2.2.1}/PKG-INFO +29 -25
  3. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/README.md +8 -0
  4. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/pyproject.toml +3 -2
  5. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient/__init__.py +1 -1
  6. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient/client.py +4 -8
  7. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient/connectionpool.py +4 -28
  8. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient/httplib.py +33 -9
  9. geventhttpclient-2.2.1/src/geventhttpclient/httplib2.py +69 -0
  10. geventhttpclient-2.2.1/src/geventhttpclient/tests/conftest.py +57 -0
  11. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient/tests/test_client.py +17 -51
  12. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient/tests/test_httplib.py +19 -21
  13. geventhttpclient-2.2.1/src/geventhttpclient/tests/test_httplib2.py +31 -0
  14. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient/tests/test_network_failures.py +2 -15
  15. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient/tests/test_ssl.py +77 -23
  16. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient/tests/test_url.py +49 -26
  17. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient/tests/test_useragent.py +1 -15
  18. geventhttpclient-2.2.1/src/geventhttpclient/url.py +160 -0
  19. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient/useragent.py +5 -5
  20. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1/src/geventhttpclient.egg-info}/PKG-INFO +29 -25
  21. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient.egg-info/SOURCES.txt +1 -2
  22. geventhttpclient-2.2.0/LICENSE.txt +0 -27
  23. geventhttpclient-2.2.0/src/geventhttpclient/httplib2.py +0 -37
  24. geventhttpclient-2.2.0/src/geventhttpclient/tests/conftest.py +0 -7
  25. geventhttpclient-2.2.0/src/geventhttpclient/tests/test_http_host.py +0 -14
  26. geventhttpclient-2.2.0/src/geventhttpclient/tests/test_httplib2.py +0 -20
  27. geventhttpclient-2.2.0/src/geventhttpclient/url.py +0 -246
  28. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/MANIFEST.in +0 -0
  29. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/ext/Python_compat.h +0 -0
  30. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/ext/_parser.c +0 -0
  31. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/llhttp/LICENSE-MIT +0 -0
  32. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/llhttp/include/llhttp.h +0 -0
  33. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/llhttp/src/api.c +0 -0
  34. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/llhttp/src/http.c +0 -0
  35. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/llhttp/src/llhttp.c +0 -0
  36. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/setup.cfg +0 -0
  37. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/setup.py +0 -0
  38. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient/header.py +0 -0
  39. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient/response.py +0 -0
  40. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient/tests/oncert.pem +0 -0
  41. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient/tests/server.crt +0 -0
  42. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient/tests/server.key +0 -0
  43. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient/tests/test_headers.py +0 -0
  44. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient/tests/test_keep_alive.py +0 -0
  45. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient/tests/test_no_module_ssl.py +0 -0
  46. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient/tests/test_parser.py +0 -0
  47. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient.egg-info/dependency_links.txt +0 -0
  48. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient.egg-info/requires.txt +0 -0
  49. {geventhttpclient-2.2.0 → geventhttpclient-2.2.1}/src/geventhttpclient.egg-info/top_level.txt +0 -0
@@ -0,0 +1,22 @@
1
+ This software is licensed under the MIT License.
2
+
3
+ Copyright Antonin Amand <antonin.amand@gmail.com>, 2018.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a
6
+ copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to permit
10
+ persons to whom the Software is furnished to do so, subject to the
11
+ following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included
14
+ in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
19
+ NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
20
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
21
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
22
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,35 +1,30 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: geventhttpclient
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: HTTP client library for gevent
5
5
  Author-email: Antonin Amand <antonin.amand@gmail.com>
6
- License: Based on llhttp, copyright Fedor Indutny, 2018.
6
+ License: This software is licensed under the MIT License.
7
7
 
8
- Python extension is copyright Antonin Amand <antonin.amand@gmail.com>,
9
- licensed under the same terms.
8
+ Copyright Antonin Amand <antonin.amand@gmail.com>, 2018.
10
9
 
11
- Permission is hereby granted, free of charge, to any person obtaining a copy
12
- of this software and associated documentation files (the "Software"), to
13
- deal in the Software without restriction, including without limitation the
14
- rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
15
- sell copies of the Software, and to permit persons to whom the Software is
16
- furnished to do so, subject to the following conditions:
10
+ Permission is hereby granted, free of charge, to any person obtaining a
11
+ copy of this software and associated documentation files (the
12
+ "Software"), to deal in the Software without restriction, including
13
+ without limitation the rights to use, copy, modify, merge, publish,
14
+ distribute, sublicense, and/or sell copies of the Software, and to permit
15
+ persons to whom the Software is furnished to do so, subject to the
16
+ following conditions:
17
17
 
18
- The above copyright notice and this permission notice shall be included in
19
- all copies or substantial portions of the Software.
18
+ The above copyright notice and this permission notice shall be included
19
+ in all copies or substantial portions of the Software.
20
20
 
21
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26
- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
27
- IN THE SOFTWARE.
28
-
29
- Note: Previous versions of gevenhttpclient used http_parser.c, which in turn
30
- was based on src/http/ngx_http_parse.c from NGINX, copyright Igor Sysoev,
31
- Joyent, Inc. and other Node contributors. See http://github.com/joyent/http-parser
32
- for more information
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
24
+ NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
25
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
26
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
27
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
33
28
 
34
29
  Project-URL: homepage, http://github.com/geventhttpclient/geventhttpclient
35
30
  Project-URL: issues, http://github.com/geventhttpclient/geventhttpclient/issues
@@ -38,6 +33,7 @@ Keywords: http,gevent,client
38
33
  Classifier: Development Status :: 5 - Production/Stable
39
34
  Classifier: Intended Audience :: Developers
40
35
  Classifier: Intended Audience :: Information Technology
36
+ Classifier: License :: OSI Approved :: MIT License
41
37
  Classifier: Operating System :: OS Independent
42
38
  Classifier: Programming Language :: Python :: 3
43
39
  Classifier: Programming Language :: Python :: 3.9
@@ -48,7 +44,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
48
44
  Classifier: Topic :: Software Development :: Libraries
49
45
  Requires-Python: >=3.9
50
46
  Description-Content-Type: text/markdown
51
- License-File: LICENSE.txt
47
+ License-File: LICENSE-MIT
52
48
  Requires-Dist: gevent
53
49
  Requires-Dist: certifi
54
50
  Requires-Dist: brotli
@@ -241,3 +237,11 @@ for mor details. Also note, HTTPX is better be used with asyncio, not gevent.
241
237
  | Httpx | 770.3 |
242
238
 
243
239
  *Linux(x86_64), Python 3.11.6 @ Intel i7-7560U*
240
+
241
+ ## License
242
+
243
+ This package is distributed under the [MIT license](https://github.com/geventhttpclient/geventhttpclient/blob/master/LICENSE-MIT).
244
+ Previous versions of geventhttpclient used http_parser.c, which in turn
245
+ was based on `src/http/ngx_http_parse.c` from NGINX, copyright Igor Sysoev,
246
+ Joyent, Inc., and other Node contributors. For more information, see
247
+ http://github.com/joyent/http-parser
@@ -174,3 +174,11 @@ for mor details. Also note, HTTPX is better be used with asyncio, not gevent.
174
174
  | Httpx | 770.3 |
175
175
 
176
176
  *Linux(x86_64), Python 3.11.6 @ Intel i7-7560U*
177
+
178
+ ## License
179
+
180
+ This package is distributed under the [MIT license](https://github.com/geventhttpclient/geventhttpclient/blob/master/LICENSE-MIT).
181
+ Previous versions of geventhttpclient used http_parser.c, which in turn
182
+ was based on `src/http/ngx_http_parse.c` from NGINX, copyright Igor Sysoev,
183
+ Joyent, Inc., and other Node contributors. For more information, see
184
+ http://github.com/joyent/http-parser
@@ -5,17 +5,18 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "geventhttpclient"
8
- version = "2.2.0" # dont forget to update version __init__.py as well
8
+ version = "2.2.1" # dont 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"
12
- license = {file = "LICENSE.txt"}
12
+ license = {file = "LICENSE-MIT"}
13
13
  keywords = ["http", "gevent", "client"]
14
14
  authors = [{name = "Antonin Amand", email = "antonin.amand@gmail.com"}]
15
15
  classifiers = [
16
16
  "Development Status :: 5 - Production/Stable",
17
17
  "Intended Audience :: Developers",
18
18
  "Intended Audience :: Information Technology",
19
+ "License :: OSI Approved :: MIT License",
19
20
  "Operating System :: OS Independent",
20
21
  "Programming Language :: Python :: 3",
21
22
  "Programming Language :: Python :: 3.9",
@@ -1,6 +1,6 @@
1
1
  # package
2
2
 
3
- __version__ = "2.2.0" # dont forget to update version in pyproject.toml as well
3
+ __version__ = "2.2.1" # dont forget to update version in pyproject.toml as well
4
4
 
5
5
  from geventhttpclient.client import HTTPClient
6
6
  from geventhttpclient.url import URL
@@ -146,14 +146,10 @@ class HTTPClient:
146
146
  self.default_headers.update(self.DEFAULT_HEADERS)
147
147
  self.default_headers.update(headers)
148
148
  self.block_size = block_size
149
- self._base_url_string = str(self.get_base_url())
150
-
151
- def get_base_url(self):
152
- url = URL()
153
- url.host = self.host
154
- url.port = self.port
155
- url.scheme = self.ssl and PROTO_HTTPS or PROTO_HTTP
156
- return url
149
+
150
+ scheme = PROTO_HTTPS if self.ssl else PROTO_HTTP
151
+ port_str = f":{port}" if port else ""
152
+ self._base_url_string = f"{scheme}://{self.host}{port_str}/"
157
153
 
158
154
  def close(self):
159
155
  self._connection_pool.close()
@@ -1,5 +1,4 @@
1
1
  import os
2
- from contextlib import contextmanager
3
2
 
4
3
  import gevent.queue
5
4
  import gevent.socket
@@ -198,12 +197,15 @@ def init_ssl_context(ssl_context_factory, ca_certs, check_hostname=True):
198
197
 
199
198
 
200
199
  try:
200
+ from ssl import PROTOCOL_TLS_CLIENT
201
+
201
202
  import gevent.ssl
202
203
 
203
204
  try:
204
205
  from gevent.ssl import create_default_context
205
206
  except ImportError:
206
207
  create_default_context = None
208
+
207
209
  except ImportError:
208
210
  pass
209
211
  else:
@@ -222,6 +224,7 @@ else:
222
224
  "ciphers": _DEFAULT_CIPHERS,
223
225
  "ca_certs": _CA_CERTS,
224
226
  "cert_reqs": gevent.ssl.CERT_REQUIRED,
227
+ "ssl_version": PROTOCOL_TLS_CLIENT,
225
228
  }
226
229
 
227
230
  def __init__(
@@ -260,30 +263,3 @@ else:
260
263
  else:
261
264
  server_hostname = self.ssl_options.get("server_hostname", self._request_host)
262
265
  return self.ssl_context.wrap_socket(sock, server_hostname=server_hostname)
263
-
264
-
265
- class ClientPool:
266
- """
267
- Pool implementation for HTTP clients, that weren't designed with concurrency in mind.
268
- Usage example:
269
-
270
- pool = ClientPool(MyHttpClient)
271
- with pool.get() as client:
272
- response = client.request("127.0.0.1")
273
- """
274
-
275
- def __init__(self, factory, concurrency=5):
276
- self.factory = factory
277
- self.queue = gevent.queue.Queue(concurrency)
278
- for i in range(concurrency):
279
- self.queue.put(factory())
280
-
281
- @contextmanager
282
- def get(self):
283
- client = self.queue.get()
284
- yield client
285
- self.queue.put(client)
286
-
287
- def close(self):
288
- for client in self.queue:
289
- client.close()
@@ -3,14 +3,15 @@ Provide HTTPConnection, HTTPSConnection and HTTPResponse implementations ready
3
3
  to use as drop-in replacements for their counterparts in http.client.
4
4
  """
5
5
 
6
- import http.client as httplib
6
+ import http.client
7
+ from contextlib import contextmanager
7
8
 
8
9
  import gevent.socket
9
10
  import gevent.ssl
10
11
 
11
12
  from geventhttpclient import connectionpool, header, response
12
13
 
13
- _UNKNOWN = getattr(httplib, "_UNKNOWN", "UNKNOWN")
14
+ _UNKNOWN = getattr(http.client, "_UNKNOWN", "UNKNOWN")
14
15
 
15
16
 
16
17
  class HTTPLibHeaders(header.Headers):
@@ -120,10 +121,7 @@ class HTTPResponse(response.HTTPSocketResponse):
120
121
  return self.status_code
121
122
 
122
123
 
123
- HTTPLibConnection = httplib.HTTPConnection
124
-
125
-
126
- class HTTPConnection(httplib.HTTPConnection):
124
+ class HTTPConnection(http.client.HTTPConnection):
127
125
  response_class = HTTPResponse
128
126
  source_address = None
129
127
  _hidden_socket = None
@@ -220,9 +218,35 @@ else:
220
218
 
221
219
 
222
220
  def patch():
223
- httplib.HTTPConnection = HTTPConnection
224
- httplib.HTTPResponse = HTTPResponse
221
+ http.client.HTTPConnection = HTTPConnection
222
+ http.client.HTTPResponse = HTTPResponse
223
+ try:
224
+ http.client.HTTPSConnection = HTTPSConnection
225
+ except NameError:
226
+ pass
227
+
228
+
229
+ @contextmanager
230
+ def patched():
231
+ """Temporarily patch http.client."""
232
+ http_client_HTTPConnection = http.client.HTTPConnection
233
+ http_client_HTTPResponse = http.client.HTTPResponse
225
234
  try:
226
- httplib.HTTPSConnection = HTTPSConnection
235
+ http_client_HTTPSConnection = http.client.HTTPSConnection
227
236
  except NameError:
228
237
  pass
238
+ try:
239
+ http.client.HTTPConnection = HTTPConnection
240
+ http.client.HTTPResponse = HTTPResponse
241
+ try:
242
+ http.client.HTTPSConnection = HTTPSConnection
243
+ except NameError:
244
+ pass
245
+ yield
246
+ finally:
247
+ http.client.HTTPConnection = http_client_HTTPConnection
248
+ http.client.HTTPResponse = http_client_HTTPResponse
249
+ try:
250
+ http.client.HTTPSConnection = http_client_HTTPSConnection
251
+ except NameError:
252
+ pass
@@ -0,0 +1,69 @@
1
+ """
2
+ Drop-in replacement for httplib2. Make sure to monkey patch everything first!
3
+
4
+ import gevent.monkey
5
+ gevent.monkey.patch_all()
6
+
7
+ from geventhttpclient import httplib
8
+ httplib.patch()
9
+
10
+ from geventhttpclient import httplib2
11
+
12
+ http = httplib2.Http(concurrency=5)
13
+ """
14
+
15
+ from contextlib import contextmanager
16
+
17
+ import gevent.queue
18
+
19
+ import geventhttpclient.httplib
20
+
21
+
22
+ class ClientPool:
23
+ """
24
+ Pool implementation for HTTP clients, that weren't designed with concurrency in mind.
25
+ Usage example:
26
+
27
+ pool = ClientPool(MyHttpClient)
28
+ with pool.get() as client:
29
+ response = client.request("127.0.0.1")
30
+ """
31
+
32
+ def __init__(self, factory, concurrency=5):
33
+ self.factory = factory
34
+ self.queue = gevent.queue.Queue(concurrency)
35
+ for i in range(concurrency):
36
+ self.queue.put(factory())
37
+
38
+ @contextmanager
39
+ def get(self):
40
+ client = self.queue.get()
41
+ yield client
42
+ self.queue.put(client)
43
+
44
+ def close(self):
45
+ for client in self.queue:
46
+ client.close()
47
+
48
+
49
+ with geventhttpclient.httplib.patched():
50
+ import httplib2
51
+
52
+ class Http:
53
+ """Imitate a httplib2.Http client. Except that it is now run concurrently."""
54
+
55
+ def __init__(self, *args, **kw):
56
+ self.concurrency = max(kw.pop("concurrency", 0), 2) # never smaller than 2
57
+ self.args = args
58
+ self.kw = kw
59
+ self.pool = ClientPool(self._http_factory, concurrency=self.concurrency)
60
+
61
+ def _http_factory(self):
62
+ return httplib2.Http(*self.args, **self.kw)
63
+
64
+ def request(self, *args, **kw):
65
+ with self.pool.get() as client:
66
+ return client.request(*args, **kw)
67
+
68
+ def close(self):
69
+ self.pool.close()
@@ -0,0 +1,57 @@
1
+ import gevent.monkey
2
+
3
+ gevent.monkey.patch_all() # make sure all tests run monkey patched
4
+
5
+ from contextlib import contextmanager
6
+
7
+ import gevent.pywsgi
8
+ import gevent.queue
9
+ import gevent.server
10
+
11
+ TEST_HOST = "127.0.0.1"
12
+ TEST_PORT = 54323
13
+ LISTENER = TEST_HOST, TEST_PORT
14
+ LISTENER_URL = f"http://{TEST_HOST}:{TEST_PORT}/"
15
+ HTTPBIN_HOST = "httpbingo.org" # this might be exchanged with a self-hosted version
16
+
17
+
18
+ @contextmanager
19
+ def server(handler):
20
+ exception_queue = gevent.queue.Queue()
21
+
22
+ def wrapped_handler(env, start_response):
23
+ try:
24
+ return handler(env, start_response)
25
+ except Exception as e:
26
+ exception_queue.put(e)
27
+ raise
28
+
29
+ server = gevent.server.StreamServer(LISTENER, handle=wrapped_handler)
30
+ server.start()
31
+ try:
32
+ yield
33
+ if not exception_queue.empty():
34
+ raise exception_queue.get()
35
+ finally:
36
+ server.stop()
37
+
38
+
39
+ @contextmanager
40
+ def wsgiserver(handler):
41
+ exception_queue = gevent.queue.Queue()
42
+
43
+ def wrapped_handler(env, start_response):
44
+ try:
45
+ return handler(env, start_response)
46
+ except Exception as e:
47
+ exception_queue.put(e)
48
+ raise
49
+
50
+ server = gevent.pywsgi.WSGIServer(LISTENER, wrapped_handler)
51
+ server.start()
52
+ try:
53
+ yield
54
+ if not exception_queue.empty():
55
+ raise exception_queue.get()
56
+ finally:
57
+ server.stop()
@@ -1,37 +1,13 @@
1
1
  import json
2
- import os
3
- from contextlib import contextmanager
4
2
 
5
3
  import gevent.pool
6
- import gevent.pywsgi
4
+ import gevent.queue
7
5
  import gevent.server
8
6
  import pytest
9
- from gevent.ssl import SSLError
10
7
 
11
- from geventhttpclient import HTTPClient, __version__
12
-
13
- LISTENER = "127.0.0.1", 54323
14
- HTTPBIN_HOST = "httpbingo.org" # this might be exchanged with a self-hosted version
15
-
16
-
17
- @contextmanager
18
- def server(handler):
19
- server = gevent.server.StreamServer(LISTENER, handle=handler)
20
- server.start()
21
- try:
22
- yield
23
- finally:
24
- server.stop()
25
-
26
-
27
- @contextmanager
28
- def wsgiserver(handler):
29
- server = gevent.pywsgi.WSGIServer(LISTENER, handler)
30
- server.start()
31
- try:
32
- yield
33
- finally:
34
- server.stop()
8
+ from geventhttpclient import __version__
9
+ from geventhttpclient.client import METHOD_GET, HTTPClient
10
+ from geventhttpclient.tests.conftest import HTTPBIN_HOST, LISTENER, server, wsgiserver
35
11
 
36
12
 
37
13
  def httpbin_client(
@@ -80,12 +56,22 @@ def httpbin():
80
56
  @pytest.mark.parametrize("request_uri", ["/tp/", "tp/", f"http://{HTTPBIN_HOST}/tp/"])
81
57
  def test_build_request(httpbin, request_uri):
82
58
  request_ref = f"GET /tp/ HTTP/1.1\r\nUser-Agent: python/gevent-http-client-{__version__}\r\nHost: {HTTPBIN_HOST}\r\n\r\n"
83
- assert httpbin._build_request("GET", request_uri) == request_ref
59
+ assert httpbin._build_request(METHOD_GET, request_uri) == request_ref
84
60
 
85
61
 
86
62
  def test_build_request_invalid_host(httpbin):
87
63
  with pytest.raises(ValueError):
88
- httpbin._build_request("GET", "http://someunrelatedhost.com/")
64
+ httpbin._build_request(METHOD_GET, "http://someunrelatedhost.com/")
65
+
66
+
67
+ @pytest.mark.parametrize("port", [None, 1234])
68
+ @pytest.mark.parametrize("host", ["localhost", "127.0.0.1", "::1", "[::1]"])
69
+ def test_build_request_host(host, port):
70
+ client = HTTPClient(host, port)
71
+ host_ref = (
72
+ f"host: {f'[{host}]' if host.startswith(':') else host}{f':{port}' if port else ''}\r\n"
73
+ )
74
+ assert host_ref in client._build_request(METHOD_GET, "").lower()
89
75
 
90
76
 
91
77
  test_url_client_args = [
@@ -255,7 +241,7 @@ def test_bytes_post():
255
241
 
256
242
 
257
243
  def test_string_post():
258
- with wsgiserver(check_upload("12345", 5)):
244
+ with wsgiserver(check_upload(b"12345", 5)):
259
245
  client = HTTPClient(*LISTENER)
260
246
  client.post("/", "12345")
261
247
 
@@ -334,26 +320,6 @@ def test_response_context_manager(httpbin):
334
320
  assert r._sock is None # released
335
321
 
336
322
 
337
- @pytest.mark.network
338
- def test_client_ssl():
339
- client = HTTPClient("github.com", ssl=True)
340
- assert client.port == 443
341
- response = client.get("/")
342
- assert response.status_code == 200
343
- body = response.read()
344
- assert len(body)
345
-
346
-
347
- @pytest.mark.network
348
- def test_ssl_fail_invalid_certificate():
349
- certs = os.path.join(os.path.dirname(os.path.abspath(__file__)), "oncert.pem")
350
- client = HTTPClient("github.com", ssl_options={"ca_certs": certs})
351
- assert client.port == 443
352
- with pytest.raises(SSLError) as e_info:
353
- client.get("/")
354
- assert e_info.value.reason == "CERTIFICATE_VERIFY_FAILED"
355
-
356
-
357
323
  @pytest.mark.network
358
324
  def test_multi_queries_greenlet_safe():
359
325
  httpbin = httpbin_client(concurrency=3)
@@ -1,23 +1,10 @@
1
+ import http.client
1
2
  import urllib.request
2
- from contextlib import contextmanager
3
- from http.client import HTTPException
4
3
 
5
- import gevent.server
6
4
  import pytest
7
5
 
8
- from geventhttpclient.httplib import HTTPConnection
9
-
10
- LISTENER = "127.0.0.1", 54323
11
-
12
-
13
- @contextmanager
14
- def server(handler):
15
- server = gevent.server.StreamServer(LISTENER, handle=handler)
16
- server.start()
17
- try:
18
- yield
19
- finally:
20
- server.stop()
6
+ from geventhttpclient.httplib import HTTPConnection, patched
7
+ from geventhttpclient.tests.conftest import HTTPBIN_HOST, LISTENER, server
21
8
 
22
9
 
23
10
  def wrong_response_status_line(sock, addr):
@@ -29,7 +16,7 @@ def test_httplib_exception():
29
16
  with server(wrong_response_status_line):
30
17
  connection = HTTPConnection(*LISTENER)
31
18
  connection.request("GET", "/")
32
- with pytest.raises(HTTPException):
19
+ with pytest.raises(http.client.HTTPException):
33
20
  connection.getresponse()
34
21
 
35
22
 
@@ -67,9 +54,20 @@ def test_msg():
67
54
  assert response.msg["Content-Type"] == "text/plain"
68
55
 
69
56
 
57
+ def test_patched():
58
+ assert http.client.HTTPResponse.__module__ == "http.client"
59
+ assert http.client.HTTPConnection.__module__ == "http.client"
60
+ with patched():
61
+ assert http.client.HTTPResponse.__module__ == "geventhttpclient.httplib"
62
+ assert http.client.HTTPConnection.__module__ == "geventhttpclient.httplib"
63
+ assert http.client.HTTPResponse.__module__ == "http.client"
64
+ assert http.client.HTTPConnection.__module__ == "http.client"
65
+
66
+
70
67
  @pytest.mark.network
71
- @pytest.mark.parametrize("url", ["http://httpbingo.org", "https://github.com"])
68
+ @pytest.mark.parametrize("url", [f"http://{HTTPBIN_HOST}", "https://github.com"])
72
69
  def test_urllib_request(url):
73
- content = urllib.request.urlopen(url).read()
74
- assert content
75
- assert b"body" in content
70
+ with patched():
71
+ content = urllib.request.urlopen(url).read()
72
+ assert content
73
+ assert b"body" in content
@@ -0,0 +1,31 @@
1
+ import gevent.pool
2
+ import pytest
3
+
4
+ import geventhttpclient.httplib
5
+
6
+ httplib2 = pytest.importorskip("geventhttpclient.httplib2")
7
+
8
+
9
+ def job(client, url):
10
+ response, content = client.request(url)
11
+ assert content
12
+ assert b"body" in content
13
+
14
+
15
+ @pytest.mark.network
16
+ def test_request_parallel():
17
+ with geventhttpclient.httplib.patched():
18
+ errors = []
19
+
20
+ client = httplib2.Http(concurrency=5)
21
+ group = gevent.pool.Pool(size=5)
22
+
23
+ urls = ["https://google.com", "http://gevent.org", "https://github.com"]
24
+ for url in urls:
25
+ g = group.spawn(job, client, url)
26
+ g.link_exception(lambda g: errors.append(g.exception))
27
+ group.join()
28
+ if errors:
29
+ for e in errors:
30
+ print(e)
31
+ raise errors[0]
@@ -1,24 +1,11 @@
1
- from contextlib import contextmanager
2
1
  from http.client import HTTPException
3
2
 
4
3
  import gevent.server
5
4
  import gevent.socket
6
5
  import pytest
7
6
 
8
- from geventhttpclient import HTTPClient
9
-
10
- CRLF = "\r\n"
11
- LISTENER = "127.0.0.1", 54323
12
-
13
-
14
- @contextmanager
15
- def server(handler):
16
- server = gevent.server.StreamServer(LISTENER, handle=handler)
17
- server.start()
18
- try:
19
- yield
20
- finally:
21
- server.stop()
7
+ from geventhttpclient.client import CRLF, HTTPClient
8
+ from geventhttpclient.tests.conftest import LISTENER, server
22
9
 
23
10
 
24
11
  def wrong_response_status_line(sock, addr):