Skip to content

Commit 47028fa

Browse files
authored
Fix for #957: allow to override default cookie jar (#963)
1 parent 6f9d172 commit 47028fa

File tree

7 files changed

+102
-54
lines changed

7 files changed

+102
-54
lines changed

aiohttp/abc.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import asyncio
22
import sys
33
from abc import ABC, abstractmethod
4+
from http.cookies import SimpleCookie
45

56

67
PY_35 = sys.version_info >= (3, 5)
@@ -68,3 +69,23 @@ def resolve(self, hostname):
6869
@abstractmethod
6970
def close(self):
7071
"""Release resolver"""
72+
73+
74+
class AbstractCookieJar(ABC):
75+
76+
def __init__(self, *, loop=None):
77+
self._cookies = SimpleCookie()
78+
self._loop = loop or asyncio.get_event_loop()
79+
80+
@property
81+
def cookies(self):
82+
"""The session cookies."""
83+
return self._cookies
84+
85+
@abstractmethod
86+
def update_cookies(self, cookies, response_url=None):
87+
"""Update cookies."""
88+
89+
@abstractmethod
90+
def filter_cookies(self, request_url):
91+
"""Returns this jar's cookies filtered by their attributes."""

aiohttp/client.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ def __init__(self, *, connector=None, loop=None, cookies=None,
3737
auth=None, request_class=ClientRequest,
3838
response_class=ClientResponse,
3939
ws_response_class=ClientWebSocketResponse,
40-
version=aiohttp.HttpVersion11):
40+
version=aiohttp.HttpVersion11,
41+
cookie_jar=None):
4142

4243
if connector is None:
4344
connector = aiohttp.TCPConnector(loop=loop)
@@ -52,7 +53,9 @@ def __init__(self, *, connector=None, loop=None, cookies=None,
5253
if loop.get_debug():
5354
self._source_traceback = traceback.extract_stack(sys._getframe(1))
5455

55-
self._cookie_jar = CookieJar(loop=loop)
56+
if cookie_jar is None:
57+
cookie_jar = CookieJar(loop=loop)
58+
self._cookie_jar = cookie_jar
5659

5760
if cookies is not None:
5861
self._cookie_jar.update_cookies(cookies)

aiohttp/helpers.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import multidict
1717

1818
from . import hdrs
19+
from .abc import AbstractCookieJar
1920
from .errors import InvalidURL
2021
try:
2122
from asyncio import ensure_future
@@ -568,7 +569,7 @@ def _cancel_task(self):
568569
self._cancelled = self._task.cancel()
569570

570571

571-
class CookieJar:
572+
class CookieJar(AbstractCookieJar):
572573
"""Implements cookie storage adhering to RFC 6265."""
573574

574575
DATE_TOKENS_RE = re.compile(
@@ -584,19 +585,10 @@ class CookieJar:
584585

585586
DATE_YEAR_RE = re.compile("(\d{2,4})")
586587

587-
def __init__(self, cookies=None, loop=None):
588-
self._cookies = SimpleCookie()
589-
self._loop = loop or asyncio.get_event_loop()
588+
def __init__(self, *, loop=None):
589+
super().__init__(loop=loop)
590590
self._host_only_cookies = set()
591591

592-
if cookies is not None:
593-
self.update_cookies(cookies)
594-
595-
@property
596-
def cookies(self):
597-
"""The session cookies."""
598-
return self._cookies
599-
600592
def _expire_cookie(self, name):
601593
if name in self._cookies:
602594
del self._cookies[name]

docs/client_reference.rst

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ The client session supports the context manager protocol for self closing.
4141
headers=None, skip_auto_headers=None, \
4242
auth=None, request_class=ClientRequest, \
4343
response_class=ClientResponse, \
44-
ws_response_class=ClientWebSocketResponse)
44+
ws_response_class=ClientWebSocketResponse,
45+
version=aiohttp.HttpVersion11,
46+
cookie_jar=None)
4547

4648
The class for creating client sessions and making requests.
4749

@@ -93,6 +95,21 @@ The client session supports the context manager protocol for self closing.
9395

9496
.. versionadded:: 0.16
9597

98+
:param version: supported HTTP version, ``HTTP 1.1`` by default.
99+
100+
.. versionadded:: 0.21
101+
102+
:param cookie_jar: Cookie Jar, :class:`AbstractCookieJar` instance.
103+
104+
By default every session instance has own private cookie jar for
105+
automatic cookies processing but user may redefine this behavior
106+
by providing own jar implementation.
107+
108+
One example is not processing cookies at all when working in
109+
proxy mode.
110+
111+
.. versionadded:: 0.22
112+
96113
.. versionchanged:: 0.16
97114
*request_class* default changed from ``None`` to ``ClientRequest``
98115

docs/web_abc.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,30 @@ attribute.
9797
.. attribute:: request
9898

9999
:class:`aiohttp.web.Request` instance for performing the request.
100+
101+
102+
Abstract Cookie Jar
103+
-------------------
104+
105+
.. class:: AbstractCookieJar(*, loop=None)
106+
107+
An abstract class for cookie storage.
108+
109+
:param loop: an :ref:`event loop<asyncio-event-loop>` instance.
110+
111+
If param is ``None`` :func:`asyncio.get_event_loop`
112+
used for getting default event loop, but we strongly
113+
recommend to use explicit loops everywhere.
114+
115+
116+
.. attribute:: cookies
117+
118+
:class:`http.cookies.SimpleCookie` instance for storing cookies info.
119+
120+
.. method:: update_cookies(cookies, response_url=None)
121+
122+
Update cookies.
123+
124+
.. method:: filter_cookies(request_url)
125+
126+
Returns this jar's cookies filtered by their attributes.

tests/test_client_session.py

Lines changed: 24 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -377,16 +377,8 @@ def test_request_ctx_manager_props(loop):
377377
def test_cookie_jar_usage(create_app_and_client):
378378
req_url = None
379379

380-
init_mock = mock.Mock(return_value=None)
381-
update_mock = mock.Mock(return_value=None)
382-
filter_mock = mock.Mock(return_value=None)
383-
384-
patches = mock.patch.multiple(
385-
"aiohttp.helpers.CookieJar",
386-
__init__=init_mock,
387-
update_cookies=update_mock,
388-
filter_cookies=filter_mock,
389-
)
380+
jar = mock.Mock()
381+
jar.filter_cookies.return_value = None
390382

391383
@asyncio.coroutine
392384
def handler(request):
@@ -397,30 +389,25 @@ def handler(request):
397389
resp.set_cookie("response", "resp_value")
398390
return resp
399391

400-
with patches:
401-
app, client = yield from create_app_and_client(
402-
client_params={"cookies": {"request": "req_value"}}
403-
)
404-
app.router.add_route('GET', '/', handler)
405-
406-
# Updating the cookie jar with initial user defined cookies
407-
assert init_mock.called
408-
assert update_mock.called
409-
assert update_mock.call_args[0] == (
410-
{"request": "req_value"},
411-
)
412-
413-
update_mock.reset_mock()
414-
yield from client.get("/")
415-
416-
# Filtering the cookie jar before sending the request,
417-
# getting the request URL as only parameter
418-
assert filter_mock.called
419-
assert filter_mock.call_args[0] == (req_url,)
420-
421-
# Updating the cookie jar with the response cookies
422-
assert update_mock.called
423-
resp_cookies = update_mock.call_args[0][0]
424-
assert isinstance(resp_cookies, http.cookies.SimpleCookie)
425-
assert "response" in resp_cookies
426-
assert resp_cookies["response"].value == "resp_value"
392+
app, client = yield from create_app_and_client(
393+
client_params={"cookies": {"request": "req_value"},
394+
"cookie_jar": jar}
395+
)
396+
app.router.add_route('GET', '/', handler)
397+
398+
# Updating the cookie jar with initial user defined cookies
399+
jar.update_cookies.assert_called_with({"request": "req_value"})
400+
401+
jar.update_cookies.reset_mock()
402+
yield from client.get("/")
403+
404+
# Filtering the cookie jar before sending the request,
405+
# getting the request URL as only parameter
406+
jar.filter_cookies.assert_called_with(req_url)
407+
408+
# Updating the cookie jar with the response cookies
409+
assert jar.update_cookies.called
410+
resp_cookies = jar.update_cookies.call_args[0][0]
411+
assert isinstance(resp_cookies, http.cookies.SimpleCookie)
412+
assert "response" in resp_cookies
413+
assert resp_cookies["response"].value == "resp_value"

tests/test_helpers.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,9 +351,10 @@ def timed_request(
351351
return cookies_sent
352352

353353
def test_constructor(self):
354-
jar = helpers.CookieJar(self.cookies_to_send, self.loop)
354+
jar = helpers.CookieJar(loop=self.loop)
355+
jar.update_cookies(self.cookies_to_send)
355356
self.assertEqual(jar.cookies, self.cookies_to_send)
356-
self.assertEqual(jar._loop, self.loop)
357+
self.assertIs(jar._loop, self.loop)
357358

358359
def test_domain_filter_ip(self):
359360
cookies_sent, cookies_received = (

0 commit comments

Comments
 (0)