Некорректная работа java.net.CookieManager


При работе с классом java.net.CookieManager иногда возникают странные баги. Например, следующий фрагмента кода будет работать с указанным URL, но с некоторыми другими не будет:

String urlString = "http://java.sun.com";
CookieManager manager = new CookieManager();
CookieHandler.setDefault(manager);

URL url = new URL(urlString);
URLConnection connection = url.openConnection();
// force cookie processing
Object content = connection.getContent();

CookieStore cookieJar = manager.getCookieStore();
List HttpCookie> cookies = cookieJar.getCookies();
for (HttpCookie cookie : cookies) {
  System.out.println(cookie);
}

Под катом подробнее.

Все дело в классе java.net.HttpCookie и методе expiryDate2DeltaSeconds. Вот его исходный код из JDK 1.6.10:

private long expiryDate2DeltaSeconds(String dateString) {
    SimpleDateFormat df = new SimpleDateFormat(NETSCAPE_COOKIE_DATE_FORMAT);
    df.setTimeZone(TimeZone.getTimeZone("GMT"));
   
    try {
        Date date = df.parse(dateString);
        return (date.getTime() - whenCreated) / 1000;
    } catch (Exception e) {
        return 0;
    }
}

Здесь просчитывается время до истечения срока действия cookie и maxAge присваивается это значение. Вызывается этот метод только если установлено значение expired и не установлено Max-Age в строке Set-Cookie (или Set-Cookie2).

Очевидно, что восстановление даты из строки зависит от текущей Locale по умолчанию, поэтому, если она отличается от Locale.US, произойдет исключительная ситуация и выполнится код в блоке catch { }. Т.е., для maxAge всегда будет возвращаться 0, если установлено значение expires и текущая локаль отличается от Locale.US.

Происходит все это при вызове java.net.HttpCookie.parse() из java.net.CookieManager.put(), который в свою очередь вызывается прозрачно при получении HTTP ответа от сервера.

Далее, при добавлении cookie в хранилище по умолчанию sun.net.www.protocol.http.InMemoryCookieStore проиcходит проверка:

// add new cookie if it has a non-zero max-age
if (cookie.getMaxAge() != 0) {
    cookieJar.add(cookie);
    // ...

Т.е. в случае maxAge==0 cookie просто не сохраняется.

Пути обхода проблемы

Самый простой, но неуклюжий способ, подходящий только для простых утилитных программ, где Locale практически не используется:

// temporary set locale to US
Locale oldLocale = Locale.getDefault();
Locale.setDefault(Locale.US);
// establish connection
URLConnection connection = url.openConnection();
// somehow get a content to force cookie parsing
// connection.getContent() is also suitable
InputStream is = connection.getInputStream();
// ...
is.close();
connection.close();
// restore locale
Locale.setDefault(oldLocale);

Способ два — написать свой класс, реализуя интерфейс CookieStore, где пересчитывать значение maxAge для каждой cookie, которую пытаются добавить в хранилище. В этом случае изменится процедура создания CookieManager:

CookieHandler.setDefault(new CookieManager(new MyCookieStore(), null));
// ...

Баг на sun.com открыт 2009-01-27, обещают, что “Will fix as soon as possible.” Читать как “Ждите выхода JDK 7″.

Полезные ссылки по теме:
Set-Cookie формат (устарел)
Set-Cookie2 формат

,

  1. No comments yet.
(will not be published)