Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update urllib3 #76

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 7 additions & 12 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ threads to test your Web app.
Supported Libaries
==================

``wsgi_intercept`` works with a variety of HTTP clients in Python 2.7,
3.5 and beyond, and in pypy.
``wsgi_intercept`` works with a variety of HTTP clients in Python 3.7
and beyond, and in pypy.

* urllib2
* urllib.request
* httplib
* http.client
* httplib2
* requests
* urllib3 (<2.0.0, urllib3 2 support is in progress)
* urllib3

How Does It Work?
=================
Expand Down Expand Up @@ -88,14 +88,9 @@ Packages Intercepted
Unfortunately each of the HTTP client libraries use their own specific
mechanism for making HTTP call-outs, so individual implementations are
needed. At this time there are implementations for ``httplib2``,
``urllib3`` (<2.0.0) and ``requests`` in both Python 2 and 3, ``urllib2`` and
``httplib`` in Python 2 and ``urllib.request`` and ``http.client``
``urllib3``, ``requests``, ``urllib.request`` and ``http.client``
in Python 3.

If you are using Python 2 and need support for a different HTTP
client, require a version of ``wsgi_intercept<0.6``. Earlier versions
include support for ``webtest``, ``webunit`` and ``zope.testbrowser``.

The best way to figure out how to use interception is to inspect
`the tests`_. More comprehensive documentation available upon
request.
Expand All @@ -115,9 +110,9 @@ it into all of the *other* Python Web testing frameworks.
The Python 2 version of wsgi-intercept was the result. Kumar McMillan
later took over maintenance.

The current version is tested with Python 2.7, 3.5-3.11, and pypy and pypy3.
It was assembled by `Chris Dent`_. Testing and documentation improvements
from `Sasha Hart`_.
The current version is tested with Python 3.7-3.12, and pypy3. It was
assembled by `Chris Dent`_. Testing and documentation improvements from
`Sasha Hart`_.

.. _"best Web testing framework":
http://blog.ianbicking.org/best-of-the-web-app-test-frameworks.html
Expand Down
8 changes: 2 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
Intended Audience :: Developers
License :: OSI Approved :: MIT License
Operating System :: OS Independent
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Expand All @@ -36,15 +34,13 @@
'license': 'MIT License',
'classifiers': CLASSIFIERS,
'packages': find_packages(),
'install_requires': [
'six',
],
'python_requires': '>=3',
'extras_require': {
'testing': [
'pytest>=2.4',
'httplib2',
'requests>=2.0.1',
'urllib3>=1.11.0,<2.0.0',
'urllib3>=2.0.0',
],
'docs': [
'sphinx',
Expand Down
4 changes: 2 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[tox]
minversion = 1.6
skipsdist = True
envlist = py27,py35,py36,py37,py38,py39,py310,py311,py312,pypy,pep8,docs,readme
envlist = py37,py38,py39,py310,py311,py312,pypy,pep8,docs,readme

[testenv]
deps = .[testing]
commands = py.test --tb=short wsgi_intercept/tests
commands = py.test --tb=short wsgi_intercept/tests {posargs}
passenv = WSGI_INTERCEPT_*

[testenv:pep8]
Expand Down
87 changes: 51 additions & 36 deletions wsgi_intercept/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@
Supported Libaries
==================

``wsgi_intercept`` works with a variety of HTTP clients in Python 2.7,
3.7 and beyond, and in pypy.
``wsgi_intercept`` works with a variety of HTTP clients in Python 3.7
and beyond, and in pypy.

* urllib2
* urllib.request
* httplib
* http.client
* httplib2
* requests
* urllib3 (<2.0.0, urllib3 2 support is in progress)
* urllib3

How Does It Work?
=================
Expand Down Expand Up @@ -88,14 +88,9 @@ def load_app():
Unfortunately each of the HTTP client libraries use their own specific
mechanism for making HTTP call-outs, so individual implementations are
needed. At this time there are implementations for ``httplib2``,
``urllib3`` (<2.0.0) and ``requests`` in both Python 2 and 3, ``urllib2`` and
``httplib`` in Python 2 and ``urllib.request`` and ``http.client``
``urllib3``, ``requests``, ``urllib.request`` and ``http.client``
in Python 3.

If you are using Python 2 and need support for a different HTTP
client, require a version of ``wsgi_intercept<0.6``. Earlier versions
include support for ``webtest``, ``webunit`` and ``zope.testbrowser``.

The best way to figure out how to use interception is to inspect
`the tests`_. More comprehensive documentation available upon
request.
Expand All @@ -115,9 +110,9 @@ def load_app():
The Python 2 version of wsgi-intercept was the result. Kumar McMillan
later took over maintenance.

The current version is tested with Python 2.7, 3.5-3.11, and pypy and pypy3.
It was assembled by `Chris Dent`_. Testing and documentation improvements
from `Sasha Hart`_.
The current version is tested with Python 3.7-3.12, and pypy3. It was
assembled by `Chris Dent`_. Testing and documentation improvements from
`Sasha Hart`_.

.. _"best Web testing framework":
http://blog.ianbicking.org/best-of-the-web-app-test-frameworks.html
Expand All @@ -144,15 +139,9 @@ def load_app():
import traceback
from io import BytesIO

# Don't use six here because it is unquote_to_bytes that we want in
# Python 3.
try:
from urllib.parse import unquote_to_bytes as url_unquote
except ImportError:
from urllib import unquote as url_unquote
from urllib.parse import unquote_to_bytes as url_unquote

import six
from six.moves.http_client import HTTPConnection, HTTPSConnection
from http.client import HTTPConnection, HTTPSConnection


# Set this to True to cause response headers from the intercepted
Expand Down Expand Up @@ -227,8 +216,7 @@ def make_environ(inp, host, port, script_name):
environ = {}

method_line = inp.readline()
if six.PY3:
method_line = method_line.decode('ISO-8859-1')
method_line = method_line.decode('ISO-8859-1')

content_type = None
content_length = None
Expand Down Expand Up @@ -302,13 +290,11 @@ def make_environ(inp, host, port, script_name):
# fill out our dictionary.
#

# In Python3 turn the bytes of the path info into a string of
# latin-1 code points, because that's what the spec says we must
# do to be like a server. Later various libraries will be forced
# to decode and then reencode to get the UTF-8 that everyone
# wants.
if six.PY3:
path_info = path_info.decode('latin-1')
# Turn the bytes of the path info into a string of latin-1 code points,
# because that's what the spec says we must do to be like a server. Later
# various libraries will be forced to decode and then reencode to get the
# UTF-8 that everyone wants.
path_info = path_info.decode('latin-1')

environ.update({
"wsgi.version": (1, 0),
Expand Down Expand Up @@ -535,6 +521,26 @@ class WSGI_HTTPConnection(HTTPConnection):
Intercept all traffic to certain hosts & redirect into a WSGI
application object.
"""

def __init__(self, *args, **kwargs):
"""
Do a complex dance to deal with urllib3's method signature
constraints.
"""
# TODO: This seems really really fragile but is passing
# tests.
if 'host' in kwargs:
host = kwargs.pop('host')
if 'port' in kwargs:
port = kwargs.pop('port')
else:
port = None
super().__init__(host, port, *args, **kwargs)
else:
if len(args) > 2:
args = args[0:2]
super().__init__(*args, **kwargs)

def get_app(self, host, port):
"""
Return the app object for the given (host, port).
Expand Down Expand Up @@ -587,6 +593,7 @@ class WSGI_HTTPSConnection(HTTPSConnection, WSGI_HTTPConnection):
Intercept all traffic to certain hosts & redirect into a WSGI
application object.
"""

def get_app(self, host, port):
"""
Return the app object for the given (host, port).
Expand Down Expand Up @@ -624,22 +631,31 @@ def connect(self):
try:
import ssl
if hasattr(self, '_context'):
# Extract cert_reqs from requests + urllib3.
# They do some of their own SSL context management
# that wsgi intercept routes around, so we need to
# be careful.
if hasattr(self, '_intercept_cert_reqs'):
cert_reqs = self._intercept_cert_reqs
else:
cert_reqs = self.cert_reqs

self._context.check_hostname = self.assert_hostname
self._check_hostname = self.assert_hostname # Py3.6
if hasattr(ssl, 'VerifyMode'):
# Support for Python3.6 and higher
if isinstance(self.cert_reqs, ssl.VerifyMode):
self._context.verify_mode = self.cert_reqs
if isinstance(cert_reqs, ssl.VerifyMode):
self._context.verify_mode = cert_reqs
else:
self._context.verify_mode = ssl.VerifyMode[
self.cert_reqs]
elif isinstance(self.cert_reqs, six.string_types):
cert_reqs]
elif isinstance(cert_reqs, str):
# Support for Python3.5 and below
self._context.verify_mode = getattr(ssl,
self.cert_reqs,
cert_reqs,
self._context.verify_mode)
else:
self._context.verify_mode = self.cert_reqs
self._context.verify_mode = cert_reqs

if not hasattr(self, 'key_file'):
self.key_file = None
Expand All @@ -658,7 +674,6 @@ def connect(self):
else:
self._check_hostname = self.check_hostname
except (ImportError, AttributeError):
import traceback
traceback.print_exc()
HTTPSConnection.connect(self)

Expand Down
34 changes: 25 additions & 9 deletions wsgi_intercept/_urllib3.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,44 @@

wsgi_fake_socket.settimeout = lambda self, timeout: None

HTTP_KEYWORD_POPS = [
'strict',
'socket_options',
'server_hostname',
]

HTTPS_KEYWORD_POPS = HTTP_KEYWORD_POPS + [
'key_password',
'server_hostname',
'cert_reqs',
'ca_certs',
'ca_cert_dir',
'assert_hostname',
'assert_fingerprint',
'ssl_version',
'ssl_minimum_version',
'ssl_maximum_version',
]


def make_urllib3_override(HTTPConnectionPool, HTTPSConnectionPool,
HTTPConnection, HTTPSConnection):

class HTTP_WSGIInterceptor(WSGI_HTTPConnection, HTTPConnection):
def __init__(self, *args, **kwargs):
if 'strict' in kwargs and sys.version_info > (3, 0):
kwargs.pop('strict')
kwargs.pop('socket_options', None)
kwargs.pop('server_hostname', None)
for kw in HTTP_KEYWORD_POPS:
kwargs.pop(kw, None)
WSGI_HTTPConnection.__init__(self, *args, **kwargs)
HTTPConnection.__init__(self, *args, **kwargs)

class HTTPS_WSGIInterceptor(WSGI_HTTPSConnection, HTTPSConnection):
is_verified = True

def __init__(self, *args, **kwargs):
if 'strict' in kwargs and sys.version_info > (3, 0):
kwargs.pop('strict')
kwargs.pop('socket_options', None)
kwargs.pop('key_password', None)
kwargs.pop('server_hostname', None)
if 'cert_reqs' in kwargs and kwargs['cert_reqs'] is not None:
self._intercept_cert_reqs = kwargs.pop("cert_reqs")
for kw in HTTPS_KEYWORD_POPS:
kwargs.pop(kw, None)
if sys.version_info > (3, 12):
kwargs.pop('key_file', None)
kwargs.pop('cert_file', None)
Expand Down
19 changes: 5 additions & 14 deletions wsgi_intercept/http_client_intercept.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
"""Intercept HTTP connections that use httplib (Py2) or http.client (Py3).
"""

try:
import http.client as http_lib
except ImportError:
import httplib as http_lib
import http.client as http_lib

from . import WSGI_HTTPConnection, WSGI_HTTPSConnection

try:
from http.client import (
HTTPConnection as OriginalHTTPConnection,
HTTPSConnection as OriginalHTTPSConnection
)
except ImportError:
from httplib import (
HTTPConnection as OriginalHTTPConnection,
HTTPSConnection as OriginalHTTPSConnection
)
from http.client import (
HTTPConnection as OriginalHTTPConnection,
HTTPSConnection as OriginalHTTPSConnection
)


class HTTP_WSGIInterceptor(WSGI_HTTPConnection, http_lib.HTTPConnection):
Expand Down
2 changes: 1 addition & 1 deletion wsgi_intercept/interceptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from importlib import import_module
from uuid import uuid4

from six.moves.urllib import parse as urlparse
from urllib import parse as urlparse

import wsgi_intercept

Expand Down
5 changes: 1 addition & 4 deletions wsgi_intercept/tests/test_http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
from wsgi_intercept import http_client_intercept, WSGIAppError
from . import wsgi_app
from .install import installer_class, skipnetwork
try:
import http.client as http_lib
except ImportError:
import httplib as http_lib
import http.client as http_lib

HOST = 'some_hopefully_nonexistant_domain'

Expand Down
10 changes: 3 additions & 7 deletions wsgi_intercept/tests/test_interceptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,9 @@
import requests
import urllib3
from httplib2 import Http, ServerNotFoundError
# don't use six as the monkey patching gets confused
try:
import http.client as http_client
except ImportError:
import httplib as http_client
from six.moves.urllib.request import urlopen
from six.moves.urllib.error import URLError
import http.client as http_client
from urllib.request import urlopen
from urllib.error import URLError

from wsgi_intercept.interceptor import (
Interceptor, HttpClientInterceptor, Httplib2Interceptor,
Expand Down
Loading
Loading