1 # -*- coding: utf-8 -*-
3 SOCKS support for urllib3
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
6 This contrib module contains provisional support for SOCKS proxies from within
7 urllib3. This module supports SOCKS4 (specifically the SOCKS4A variant) and
8 SOCKS5. To enable its functionality, either install PySocks or install this
9 module with the ``socks`` extra.
13 - Currently PySocks does not support contacting remote websites via literal
14 IPv6 addresses. Any such connection attempt will fail.
15 - Currently PySocks does not support IPv6 connections to the SOCKS proxy. Any
16 such connection attempt will fail.
18 from __future__ import absolute_import
24 from ..exceptions import DependencyWarning
27 'SOCKS support in urllib3 requires the installation of optional '
28 'dependencies: specifically, PySocks. For more information, see '
29 'https://urllib3.readthedocs.org/en/latest/contrib.html#socks-proxies'
35 from socket import error as SocketError, timeout as SocketTimeout
37 from ..connection import (
38 HTTPConnection, HTTPSConnection
40 from ..connectionpool import (
41 HTTPConnectionPool, HTTPSConnectionPool
43 from ..exceptions import ConnectTimeoutError, NewConnectionError
44 from ..poolmanager import PoolManager
45 from ..util.url import parse_url
53 class SOCKSConnection(HTTPConnection):
55 A plain-text HTTP connection that connects via a SOCKS proxy.
57 def __init__(self, *args, **kwargs):
58 self._socks_options = kwargs.pop('_socks_options')
59 super(SOCKSConnection, self).__init__(*args, **kwargs)
63 Establish a new connection via the SOCKS proxy.
66 if self.source_address:
67 extra_kw['source_address'] = self.source_address
69 if self.socket_options:
70 extra_kw['socket_options'] = self.socket_options
73 conn = socks.create_connection(
74 (self.host, self.port),
75 proxy_type=self._socks_options['socks_version'],
76 proxy_addr=self._socks_options['proxy_host'],
77 proxy_port=self._socks_options['proxy_port'],
78 proxy_username=self._socks_options['username'],
79 proxy_password=self._socks_options['password'],
84 except SocketTimeout as e:
85 raise ConnectTimeoutError(
86 self, "Connection to %s timed out. (connect timeout=%s)" %
87 (self.host, self.timeout))
89 except socks.ProxyError as e:
90 # This is fragile as hell, but it seems to be the only way to raise
94 if isinstance(error, SocketTimeout):
95 raise ConnectTimeoutError(
97 "Connection to %s timed out. (connect timeout=%s)" %
98 (self.host, self.timeout)
101 raise NewConnectionError(
103 "Failed to establish a new connection: %s" % error
106 raise NewConnectionError(
108 "Failed to establish a new connection: %s" % e
111 except SocketError as e: # Defensive: PySocks should catch all these.
112 raise NewConnectionError(
113 self, "Failed to establish a new connection: %s" % e)
118 # We don't need to duplicate the Verified/Unverified distinction from
119 # urllib3/connection.py here because the HTTPSConnection will already have been
120 # correctly set to either the Verified or Unverified form by that module. This
121 # means the SOCKSHTTPSConnection will automatically be the correct type.
122 class SOCKSHTTPSConnection(SOCKSConnection, HTTPSConnection):
126 class SOCKSHTTPConnectionPool(HTTPConnectionPool):
127 ConnectionCls = SOCKSConnection
130 class SOCKSHTTPSConnectionPool(HTTPSConnectionPool):
131 ConnectionCls = SOCKSHTTPSConnection
134 class SOCKSProxyManager(PoolManager):
136 A version of the urllib3 ProxyManager that routes connections via the
139 pool_classes_by_scheme = {
140 'http': SOCKSHTTPConnectionPool,
141 'https': SOCKSHTTPSConnectionPool,
144 def __init__(self, proxy_url, username=None, password=None,
145 num_pools=10, headers=None, **connection_pool_kw):
146 parsed = parse_url(proxy_url)
148 if parsed.scheme == 'socks5':
149 socks_version = socks.PROXY_TYPE_SOCKS5
150 elif parsed.scheme == 'socks4':
151 socks_version = socks.PROXY_TYPE_SOCKS4
154 "Unable to determine SOCKS version from %s" % proxy_url
157 self.proxy_url = proxy_url
160 'socks_version': socks_version,
161 'proxy_host': parsed.host,
162 'proxy_port': parsed.port,
163 'username': username,
164 'password': password,
166 connection_pool_kw['_socks_options'] = socks_options
168 super(SOCKSProxyManager, self).__init__(
169 num_pools, headers, **connection_pool_kw
172 self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme