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

Refactored ProxyConnector #998

Merged
merged 2 commits into from
Jul 24, 2016
Merged
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
23 changes: 17 additions & 6 deletions aiohttp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ def request(self, method, url, *,
compress=None,
chunked=None,
expect100=False,
read_until_eof=True):
read_until_eof=True,
proxy=None,
proxy_auth=None):
"""Perform HTTP request."""

return _RequestContextManager(
Expand All @@ -123,7 +125,9 @@ def request(self, method, url, *,
compress=compress,
chunked=chunked,
expect100=expect100,
read_until_eof=read_until_eof))
read_until_eof=read_until_eof,
proxy=proxy,
proxy_auth=proxy_auth,))

@asyncio.coroutine
def _request(self, method, url, *,
Expand All @@ -139,7 +143,9 @@ def _request(self, method, url, *,
compress=None,
chunked=None,
expect100=False,
read_until_eof=True):
read_until_eof=True,
proxy=None,
proxy_auth=None):

if version is not None:
warnings.warn("HTTP version should be specified "
Expand Down Expand Up @@ -181,7 +187,8 @@ def _request(self, method, url, *,
cookies=cookies, encoding=encoding,
auth=auth, version=version, compress=compress, chunked=chunked,
expect100=expect100,
loop=self._loop, response_class=self._response_class)
loop=self._loop, response_class=self._response_class,
proxy=proxy, proxy_auth=proxy_auth,)

conn = yield from self._connector.connect(req)
try:
Expand Down Expand Up @@ -621,7 +628,9 @@ def request(method, url, *,
loop=None,
read_until_eof=True,
request_class=None,
response_class=None):
response_class=None,
proxy=None,
proxy_auth=None):
"""Constructs and sends a request. Returns response object.

:param str method: HTTP method
Expand Down Expand Up @@ -692,7 +701,9 @@ def request(method, url, *,
compress=compress,
chunked=chunked,
expect100=expect100,
read_until_eof=read_until_eof),
read_until_eof=read_until_eof,
proxy=proxy,
proxy_auth=proxy_auth,),
session=session)


Expand Down
12 changes: 11 additions & 1 deletion aiohttp/client_reqrep.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ def __init__(self, method, url, *,
auth=None, encoding='utf-8',
version=aiohttp.HttpVersion11, compress=None,
chunked=None, expect100=False,
loop=None, response_class=None):
loop=None, response_class=None,
proxy=None, proxy_auth=None):

if loop is None:
loop = asyncio.get_event_loop()
Expand All @@ -91,6 +92,7 @@ def __init__(self, method, url, *,
self.update_cookies(cookies)
self.update_content_encoding(data)
self.update_auth(auth)
self.update_proxy(proxy, proxy_auth)

self.update_body_from_data(data, skip_auto_headers)
self.update_transfer_encoding()
Expand Down Expand Up @@ -366,6 +368,14 @@ def update_expect_continue(self, expect=False):
if expect:
self._continue = helpers.create_future(self.loop)

def update_proxy(self, proxy, proxy_auth):
if proxy and not proxy.startswith('http://'):
raise ValueError("Only http proxies are supported")
if proxy_auth and not isinstance(proxy_auth, helpers.BasicAuth):
raise ValueError("proxy_auth must be None or BasicAuth() tuple")
self.proxy = proxy
self.proxy_auth = proxy_auth

@asyncio.coroutine
def write_bytes(self, request, reader):
"""Support coroutines that yields bytes objects."""
Expand Down
106 changes: 60 additions & 46 deletions aiohttp/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from .errors import HttpProxyError, ProxyConnectionError
from .errors import ClientOSError, ClientTimeoutError
from .errors import FingerprintMismatch
from .helpers import BasicAuth, is_ip_address
from .helpers import is_ip_address
from .resolver import DefaultResolver


Expand Down Expand Up @@ -595,6 +595,15 @@ def _create_connection(self, req):

Has same keyword arguments as BaseEventLoop.create_connection.
"""
if req.proxy:
transport, proto = yield from self._create_proxy_connection(req)
else:
transport, proto = yield from self._create_direct_connection(req)

return transport, proto

@asyncio.coroutine
def _create_direct_connection(self, req):
if req.ssl:
sslcontext = self.ssl_context
else:
Expand Down Expand Up @@ -637,56 +646,17 @@ def _create_connection(self, req):
'Can not connect to %s:%s [%s]' %
(req.host, req.port, exc.strerror)) from exc


class ProxyConnector(TCPConnector):
"""Http Proxy connector.

:param str proxy: Proxy URL address. Only HTTP proxy supported.
:param proxy_auth: (optional) Proxy HTTP Basic Auth
:type proxy_auth: aiohttp.helpers.BasicAuth
:param args: see :class:`TCPConnector`
:param kwargs: see :class:`TCPConnector`

Usage:

>>> conn = ProxyConnector(proxy="http://some.proxy.com")
>>> session = ClientSession(connector=conn)
>>> resp = yield from session.get('http://python.org')

"""

def __init__(self, proxy, *, proxy_auth=None, force_close=True,
**kwargs):
super().__init__(force_close=force_close, **kwargs)
self._proxy = proxy
self._proxy_auth = proxy_auth
assert proxy.startswith('http://'), (
"Only http proxy supported", proxy)
assert proxy_auth is None or isinstance(proxy_auth, BasicAuth), (
"proxy_auth must be None or BasicAuth() tuple", proxy_auth)

@property
def proxy(self):
"""Proxy URL."""
return self._proxy

@property
def proxy_auth(self):
"""Proxy auth info.

Should be BasicAuth instance.
"""
return self._proxy_auth

@asyncio.coroutine
def _create_connection(self, req):
def _create_proxy_connection(self, req):
proxy_req = ClientRequest(
hdrs.METH_GET, self._proxy,
hdrs.METH_GET, req.proxy,
headers={hdrs.HOST: req.host},
auth=self._proxy_auth,
auth=req.proxy_auth,
loop=self._loop)
try:
transport, proto = yield from super()._create_connection(proxy_req)
# create connection to proxy server
transport, proto = yield from self._create_direct_connection(
proxy_req)
except OSError as exc:
raise ProxyConnectionError(*exc.args) from exc

Expand Down Expand Up @@ -743,6 +713,50 @@ def _create_connection(self, req):
return transport, proto


class ProxyConnector(TCPConnector):
"""Http Proxy connector.
Deprecated, use ClientSession.request with proxy parameters.
Is still here for backward compatibility.

:param str proxy: Proxy URL address. Only HTTP proxy supported.
:param proxy_auth: (optional) Proxy HTTP Basic Auth
:type proxy_auth: aiohttp.helpers.BasicAuth
:param args: see :class:`TCPConnector`
:param kwargs: see :class:`TCPConnector`

Usage:

>>> conn = ProxyConnector(proxy="http://some.proxy.com")
>>> session = ClientSession(connector=conn)
>>> resp = yield from session.get('http://python.org')

"""

def __init__(self, proxy, *, proxy_auth=None, force_close=True,
**kwargs):
super().__init__(force_close=force_close, **kwargs)
self._proxy = proxy
self._proxy_auth = proxy_auth

@property
def proxy(self):
return self._proxy

@property
def proxy_auth(self):
return self._proxy_auth

@asyncio.coroutine
def _create_connection(self, req):
"""
Use TCPConnector _create_connection, to emulate old ProxyConnector.
"""
req.update_proxy(self._proxy, self._proxy_auth)
transport, proto = yield from super()._create_connection(req)

return transport, proto


class UnixConnector(BaseConnector):
"""Unix socket connector.

Expand Down
29 changes: 15 additions & 14 deletions docs/client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -518,26 +518,27 @@ Proxy support
-------------

aiohttp supports proxy. You have to use
:class:`~aiohttp.ProxyConnector`::
:attr:`proxy`::

conn = aiohttp.ProxyConnector(proxy="http://some.proxy.com")
session = aiohttp.ClientSession(connector=conn)
async with session.get('http://python.org') as resp:
print(resp.status)
async with aiohttp.ClientSession() as session:
async with session.get("http://python.org",
proxy="http://some.proxy.com") as resp:
print(resp.status)

:class:`~aiohttp.ProxyConnector` also supports proxy authorization::
it also supports proxy authorization::

conn = aiohttp.ProxyConnector(
proxy="http://some.proxy.com",
proxy_auth=aiohttp.BasicAuth('user', 'pass'))
session = aiohttp.ClientSession(connector=conn)
async with session.get('http://python.org') as r:
assert r.status == 200

async with aiohttp.ClientSession() as session:
proxy_auth = aiohttp.BasicAuth('user', 'pass')
async with session.get("http://python.org",
proxy="http://some.proxy.com",
proxy_auth=proxy_auth) as resp:
print(resp.status)

Authentication credentials can be passed in proxy URL::

conn = aiohttp.ProxyConnector(
proxy="http://user:pass@some.proxy.com")
session.get("http://python.org",
proxy="http://user:pass@some.proxy.com")


Response Status Codes
Expand Down
19 changes: 17 additions & 2 deletions docs/client_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ The client session supports the context manager protocol for self closing.
max_redirects=10, encoding='utf-8',\
version=HttpVersion(major=1, minor=1),\
compress=None, chunked=None, expect100=False,\
read_until_eof=True)
read_until_eof=True,\
proxy=None, proxy_auth=None)
:async-with:
:coroutine:

Expand Down Expand Up @@ -209,9 +210,18 @@ The client session supports the context manager protocol for self closing.
does not have Content-Length header.
``True`` by default (optional).

:param str proxy: Proxy URL (optional)

:param aiohttp.BasicAuth proxy_auth: an object that represents proxy HTTP
Basic Authorization (optional)

:return ClientResponse: a :class:`client response
<ClientResponse>` object.

.. versionadded:: 0.23

Added :attr:`proxy` and :attr:`proxy_auth` parameters.

.. comethod:: get(url, *, allow_redirects=True, **kwargs)
:async-with:
:coroutine:
Expand Down Expand Up @@ -674,7 +684,7 @@ There are standard connectors:

1. :class:`TCPConnector` for regular *TCP sockets* (both *HTTP* and
*HTTPS* schemes supported).
2. :class:`ProxyConnector` for connecting via HTTP proxy.
2. :class:`ProxyConnector` for connecting via HTTP proxy (deprecated).
3. :class:`UnixConnector` for connecting via UNIX socket (it's used mostly for
testing purposes).

Expand Down Expand Up @@ -948,6 +958,11 @@ ProxyConnector

:class:`ProxyConnector` is inherited from :class:`TCPConnector`.

.. deprecated:: 0.23

Use :meth:`ClientSession.request` with :attr:`proxy` and :attr:`proxy_auth`
parameters.

Usage::

conn == ProxyConnector(proxy="http://some.proxy.com")
Expand Down
Loading