Skip to content

Commit

Permalink
New urllib3.connection module.
Browse files Browse the repository at this point in the history
  • Loading branch information
shazow committed Oct 16, 2013
1 parent 3b6d57e commit 33ecc78
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 115 deletions.
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ dev (master)
* Improved url parsing in ``urllib3.util.parse_url`` (properly parse '@' in
username, and blank ports like 'hostname:').

* New `urllib3.connection` module which contains all the HTTPConnection
objects.

* ...


1.7.1 (2013-09-25)
++++++++++++++++++
Expand Down
17 changes: 0 additions & 17 deletions test/with_dummyserver/test_https.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,23 +239,6 @@ def new_pool(timeout, cert_reqs='CERT_REQUIRED'):
self.assertRaises(ConnectTimeoutError, https_pool.request, 'GET', '/',
timeout=Timeout(total=None, connect=0.001))

# FIXME(kevinburke): What is this testing? It's currently broken.
"""
https_pool = new_pool(t, cert_reqs='CERT_NONE')
conn = https_pool._new_conn()
try:
conn.set_tunnel(self.host, self.port)
except AttributeError: # Python 2.6
conn._set_tunnel(self.host, self.port)
conn._tunnel = mock.Mock()
try:
https_pool._make_request(conn, 'GET', '/')
except AttributeError:
# wrap_socket unavailable when you mock out ssl
pass
conn._tunnel.assert_called_once_with()
"""

def test_enhanced_ssl_connection(self):
conn = VerifiedHTTPSConnection(self.host, self.port)
https_pool = HTTPSConnectionPool(self.host, self.port,
Expand Down
107 changes: 107 additions & 0 deletions urllib3/connection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# urllib3/connection.py
# Copyright 2008-2013 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
#
# This module is part of urllib3 and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php

import socket
from socket import timeout as SocketTimeout

try: # Python 3
from http.client import HTTPConnection, HTTPException
except ImportError:
from httplib import HTTPConnection, HTTPException

class DummyConnection(object):
"Used to detect a failed ConnectionCls import."
pass

try: # Compiled with SSL?
ssl = None
HTTPSConnection = DummyConnection

class BaseSSLError(BaseException):
pass

try: # Python 3
from http.client import HTTPSConnection
except ImportError:
from httplib import HTTPSConnection

import ssl
BaseSSLError = ssl.SSLError

except (ImportError, AttributeError): # Platform-specific: No SSL.
pass

from .exceptions import (
ConnectTimeoutError,
)
from .packages.ssl_match_hostname import match_hostname
from .util import (
assert_fingerprint,
resolve_cert_reqs,
resolve_ssl_version,
ssl_wrap_socket,
)

class VerifiedHTTPSConnection(HTTPSConnection):
"""
Based on httplib.HTTPSConnection but wraps the socket with
SSL certification.
"""
cert_reqs = None
ca_certs = None
ssl_version = None

def set_cert(self, key_file=None, cert_file=None,
cert_reqs=None, ca_certs=None,
assert_hostname=None, assert_fingerprint=None):

self.key_file = key_file
self.cert_file = cert_file
self.cert_reqs = cert_reqs
self.ca_certs = ca_certs
self.assert_hostname = assert_hostname
self.assert_fingerprint = assert_fingerprint

def connect(self):
# Add certificate verification
try:
sock = socket.create_connection(
address=(self.host, self.port),
timeout=self.timeout,
)
except SocketTimeout:
raise ConnectTimeoutError(
self, "Connection to %s timed out. (connect timeout=%s)" %
(self.host, self.timeout))

resolved_cert_reqs = resolve_cert_reqs(self.cert_reqs)
resolved_ssl_version = resolve_ssl_version(self.ssl_version)

if self._tunnel_host:
self.sock = sock
# Calls self._set_hostport(), so self.host is
# self._tunnel_host below.
self._tunnel()

# Wrap socket using verification with the root certs in
# trusted_root_certs
self.sock = ssl_wrap_socket(sock, self.key_file, self.cert_file,
cert_reqs=resolved_cert_reqs,
ca_certs=self.ca_certs,
server_hostname=self.host,
ssl_version=resolved_ssl_version)

if resolved_cert_reqs != ssl.CERT_NONE:
if self.assert_fingerprint:
assert_fingerprint(self.sock.getpeercert(binary_form=True),
self.assert_fingerprint)
elif self.assert_hostname is not False:
match_hostname(self.sock.getpeercert(),
self.assert_hostname or self.host)


if ssl:
HTTPSConnection = VerifiedHTTPSConnection

This comment has been minimized.

Copy link
@kevinburke

kevinburke Nov 18, 2013

Contributor

This is kinda killing the ability to test the standalone HTTPSConnection introduced in #259 , because the class just gets clobbered. Here are ideas I have to get around this

  • testing standalone HTTPSConnection in its own test module
  • initializing some HAS_SSL var to be False in a test, re-importing the module (not sure if this would even work)
  • determining which class to use at runtime, instead of import time
  • naming the module _HTTPSConnection or something and then importing that directly in the test, so no clobbering

This comment has been minimized.

Copy link
@kevinburke

kevinburke Nov 18, 2013

Contributor

or, just mark connect() logic in HTTPSConnection as platformspecific and don't test it.

108 changes: 10 additions & 98 deletions urllib3/connectionpool.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,13 @@
from socket import error as SocketError, timeout as SocketTimeout
import socket

try: # Python 3
from http.client import HTTPConnection, HTTPException
from http.client import HTTP_PORT, HTTPS_PORT
except ImportError:
from httplib import HTTPConnection, HTTPException
from httplib import HTTP_PORT, HTTPS_PORT

try: # Python 3
from queue import LifoQueue, Empty, Full
except ImportError:
from Queue import LifoQueue, Empty, Full
import Queue as _ # Platform-specific: Windows


class DummyConnection(object):
"Used to detect a failed ConnectionCls import."
pass

try: # Compiled with SSL?
ssl = None
HTTPSConnection = DummyConnection

class BaseSSLError(Exception):
pass

try: # Python 3
from http.client import HTTPSConnection
except ImportError:
from httplib import HTTPSConnection

import ssl
BaseSSLError = ssl.SSLError

except (ImportError, AttributeError): # Platform-specific: No SSL.
pass


from .exceptions import (
ClosedPoolError,
ConnectTimeoutError,
Expand All @@ -57,91 +27,35 @@ class BaseSSLError(Exception):
ReadTimeoutError,
ProxyError,
)
from .packages.ssl_match_hostname import CertificateError, match_hostname
from .packages.ssl_match_hostname import CertificateError
from .packages import six
from .connection import (
DummyConnection,
HTTPConnection, HTTPSConnection, VerifiedHTTPSConnection,
HTTPException, BaseSSLError,
)
from .request import RequestMethods
from .response import HTTPResponse
from .util import (
assert_fingerprint,
get_host,
is_connection_dropped,
resolve_cert_reqs,
resolve_ssl_version,
ssl_wrap_socket,
Timeout,
)


xrange = six.moves.xrange

log = logging.getLogger(__name__)

_Default = object()

port_by_scheme = {
'http': HTTP_PORT,
'https': HTTPS_PORT,
'http': 80,
'https': 443,
}


## Connection objects (extension of httplib)

class VerifiedHTTPSConnection(HTTPSConnection):
"""
Based on httplib.HTTPSConnection but wraps the socket with
SSL certification.
"""
cert_reqs = None
ca_certs = None
ssl_version = None

def set_cert(self, key_file=None, cert_file=None,
cert_reqs=None, ca_certs=None,
assert_hostname=None, assert_fingerprint=None):

self.key_file = key_file
self.cert_file = cert_file
self.cert_reqs = cert_reqs
self.ca_certs = ca_certs
self.assert_hostname = assert_hostname
self.assert_fingerprint = assert_fingerprint

def connect(self):
# Add certificate verification
try:
sock = socket.create_connection(
address=(self.host, self.port),
timeout=self.timeout)
except SocketTimeout:
raise ConnectTimeoutError(
self, "Connection to %s timed out. (connect timeout=%s)" %
(self.host, self.timeout))

resolved_cert_reqs = resolve_cert_reqs(self.cert_reqs)
resolved_ssl_version = resolve_ssl_version(self.ssl_version)

if self._tunnel_host:
self.sock = sock
# Calls self._set_hostport(), so self.host is
# self._tunnel_host below.
self._tunnel()

# Wrap socket using verification with the root certs in
# trusted_root_certs
self.sock = ssl_wrap_socket(sock, self.key_file, self.cert_file,
cert_reqs=resolved_cert_reqs,
ca_certs=self.ca_certs,
server_hostname=self.host,
ssl_version=resolved_ssl_version)

if resolved_cert_reqs != ssl.CERT_NONE:
if self.assert_fingerprint:
assert_fingerprint(self.sock.getpeercert(binary_form=True),
self.assert_fingerprint)
elif self.assert_hostname is not False:
match_hostname(self.sock.getpeercert(),
self.assert_hostname or self.host)


## Pool objects

class ConnectionPool(object):
Expand Down Expand Up @@ -259,6 +173,7 @@ def _new_conn(self):
self.num_connections += 1
log.info("Starting new HTTP connection (%d): %s" %
(self.num_connections, self.host))

extra_params = {}
if not six.PY3: # Python 2
extra_params['strict'] = self.strict
Expand All @@ -267,7 +182,6 @@ def _new_conn(self):
timeout=self.timeout.connect_timeout,
**extra_params)


def _get_conn(self, timeout=None):
"""
Get a connection. Will return a pooled connection if one is available.
Expand Down Expand Up @@ -644,8 +558,6 @@ class HTTPSConnectionPool(HTTPConnectionPool):

scheme = 'https'
ConnectionCls = HTTPSConnection
if ssl:
ConnectionCls = VerifiedHTTPSConnection

def __init__(self, host, port=None,
strict=False, timeout=None, maxsize=1,
Expand Down

0 comments on commit 33ecc78

Please sign in to comment.