aiohttp.ClientSession не устанавливает cook ie с ключом 'expires' - PullRequest
4 голосов
/ 08 января 2020

У меня есть фиктивный сервер, который отправляет повара ie с Set-Cookie, которому я звоню с aiohttp.ClienSession.

Когда я отправляю простого повара ie с именем и значением сеанс хранит его в банке ie. Однако, если я добавлю ключ expires, то повар ie вообще не будет сохранен.

Это происходит только с ключом expires, а не с другими стандартными ключами. Кроме того, когда я использую requests.Session вместо этого, повар ie правильно сохраняется.

Есть ли проблема с тем, как я отправляю повара ie?


Вот код сервера:

import flask

app = flask.Flask(__name__)

@app.route("/cookie/<name>/<value>")
def send_cookie(name, value):
    cookie = f"{name}={value}"
    response = flask.Response(status=200, headers={"Set-Cookie": cookie})
    return response

@app.route("/cookie/expires/<name>/<value>")
def send_expiring_cookie(name, value):
    cookie = f"{name}={value}; expires=Wed, 15 Jan 2020 09:45:07 -0000"
    response = flask.Response(status=200, headers={"Set-Cookie": cookie})
    return response

app.run("localhost")

С aiohttp.ClientSession:

import asyncio as aio
import aiohttp

async def main():
    async with aiohttp.ClientSession() as session:
        response = await session.get("http://localhost:5000/cookie/hello/world")
        print(session.cookie_jar._cookies)
        # defaultdict(<class 'http.cookies.SimpleCookie'>, {'localhost': <SimpleCookie: hello='world'>})

    async with aiohttp.ClientSession() as session:
        response = await session.get("http://localhost:5000/cookie/expires/hello/world")
        print(session.cookie_jar._cookies)
        # defaultdict(<class 'http.cookies.SimpleCookie'>, {})

loop = aio.get_event_loop()
loop.run_until_complete(main())

С requests.Session:

import requests

with requests.Session() as session:
    session.get("http://localhost:5000/cookie/hello/world")
    print(session.cookies)
    # <RequestsCookieJar[<Cookie hello=world for localhost.local/cookie/hello>]>

with requests.Session() as session:
    session.get("http://localhost:5000/cookie/expires/hello/world")
    print(session.cookies)
    # <RequestsCookieJar[<Cookie hello=world for localhost.local/cookie/expires/hello>]

Ответы [ 2 ]

2 голосов
/ 08 января 2020

Во-первых, спасибо за прекрасный пример работы!

Мне удалось заставить ваше решение хранить повара ie, сделав экранирование пробелов в поле expires, например так:

import re

import flask

app = flask.Flask(__name__)

@app.route("/cookie/<name>/<value>")
def send_cookie(name, value):
    cookie = f"{name}={value}"
    response = flask.Response(status=200, headers={"Set-Cookie": cookie})
    return response

@app.route("/cookie/expires/<name>/<value>")
def send_expiring_cookie(name, value):
    exp_date = re.escape("Wed, 15 Jan 2020 09:45:07 -0000")
    cookie = f"{name}={value}; expires={exp_date}"
    response = flask.Response(status=200, headers={"Set-Cookie": cookie})
    return response

app.run("localhost")

Я думаю, что проблема в том, что aiohttp разбирает дату. Я не знаю, что, по их утверждению, должно работать, но оно определенно использует регулярное выражение для разбора даты, поэтому есть смысл избегать ее.

1 голос
/ 08 января 2020

Причина, по которой повар ie не установлен, заключается в том, что он не был успешно проанализирован. Модуль aiohttp опирается на http.cookies из стандартной библиотеки и его класса SimpleCookie.

При получении ответа его заголовки проверяются на наличие ключа Set-Cookie, а значение, связанное с ним, равно используется для создания экземпляра SimpleCookie. Последний имеет метод load, который анализирует строку повара ie и извлекает информацию о поваре ie. Для этого строка cook ie сопоставляется с регулярным выражением _CookiePattern, определенным в том же модуле в строке 434 (для тега v3.7.2):

_CookiePattern = re.compile(r"""
    \s*                            # Optional whitespace at start of cookie
    (?P<key>                       # Start of group 'key'
    [""" + _LegalKeyChars + r"""]+?   # Any word of at least one letter
    )                              # End of group 'key'
    (                              # Optional group: there may not be a value.
    \s*=\s*                          # Equal Sign
    (?P<val>                         # Start of group 'val'
    "(?:[^\\"]|\\.)*"                  # Any doublequoted string
    |                                  # or
    \w{3},\s[\w\d\s-]{9,11}\s[\d:]{8}\sGMT  # Special case for "expires" attr
    |                                  # or
    [""" + _LegalValueChars + r"""]*      # Any word or empty string
    )                                # End of group 'val'
    )?                             # End of optional value group
    \s*                            # Any number of spaces.
    (\s+|;|$)                      # Ending either at space, semicolon, or EOS.
    """, re.ASCII | re.VERBOSE)    # re.ASCII may be removed if safe.

Из этого шаблона мы можно прочитать, что ожидаемый шаблон для атрибута expires:

\w{3},\s[\w\d\s-]{9,11}\s[\d:]{8}\sGMT

Этот шаблон реализует (в несколько запутанном и не совсем точном, но все же коротком виде) формат даты, указанный в MDN docs .

В результате мой повар ie, чье поле expires равно expires=Wed, 15 Jan 2020 09:45:07 -0000, естественно игнорируется, поскольку часовой пояс выражается как -0000, тогда как ожидается, что он будет GMT , Кстати, вот что MDN документы говорят об этом:

GMT

Среднее время по Гринвичу. Даты HTTP всегда выражаются в GMT, а не в местном времени.

Вывод таков: мой повар ie просто уродлив. И неудивительно, что замена -0000 на GMT работает.

...