Pull to refresh

Ошибка склеивания нескольких Set-Cookie применительно к urllib2/mechanize и её исправление (для Python)

Reading time 2 min
Views 2.1K
Возможно, кто-то из читателей сталкивался с этой проблемой. В багтрекере GAE она уже давно висит в виде незакрытого Issue 3379. Кажется, изначально проблема касалась только Java, но сейчас она наблюдается и в Python (по крайней мере в 2.7). Описание ошибки и решение для Java можно найти, например, там, а в этом топике речь пойдёт про Python.

Коротко о сути. Часто сайты пытаются установить более одной cookie за раз. Делают они это путём указания нескольких заголовков Set-Cookie в ответе на запрос. По странному ведёт себя в этом случае urlfetch (и базирующиеся на нём urllib/urllib2): все эти заголовки склеиваются в один и разделяются запятыми. Надо ли напоминать, что запятые также присутствуют в полях expiries, а порой и в самих значениях cookie, что очень затрудняет обратный разбор такой строки. А стандартный HTTPCookieProcessor из urllib2 и mechanize просто не справляется с такой ситуацией.

Итак, если ваш проект использует поддержку cookies «из коробки» в urllib2 или mechanize, то вам безусловно подойдёт следующее простое решение.

Создадим обработчик, разделяющий проблемные заголовки до того, как они перейдут к HTTPCookieProcessor:
import urllib2, re

class SplitCookiesHandler(urllib2.BaseHandler):
    handler_order = 0
    def http_response(self, req, response):
        headers = response.info().headers
        for h in headers[:]:
            matched = re.match("(?i)set-cookie:(.*)\n$", h)
            if matched is not None:
                headers.remove(h)
                for cookie in re.split(",(?= \w+[\w\d]*=)", matched.group(1)):
                    headers.append("set-cookie:" + cookie + "\n")
        return response


Используемое в качестве разделителя регулярное выражение может давать в тяжёлых случаях ложные срабатывания, но я с таким кривыми cookie на практике не сталкивался.

Осталось только добавить наш обработчик в новый OpenDirector (или в имеющийся с помощью add_handler()):
import cookielib

cj = cookielib.CookieJar()
opener = urllib2.build_opener(SplitCookiesHandler(), urllib2.HTTPCookieProcessor(cj))


Всё, пользуемся:
r = opener.open("http://www.yandex.ru/")


В случае, если вы (как и я) используете mechanize, просто замените везде urllib2 на mechanize, а вместо mechanize.build_opener() естественнее воспользоваться следующей конструкцией:
br = mechanize.Browser()
br.add_handler(SplitCookiesHandler())
Tags:
Hubs:
+10
Comments 2
Comments Comments 2

Articles