多线程下的CookieManager

10

我正在尝试通过线程建立多个连接。

但每个连接似乎会覆盖其他连接的cookies,导致连接使用错误的cookies。

在线程类的构造函数内:

    manager = new CookieManager();
    manager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
    CookieHandler.setDefault(manager);

有没有办法按线程或类别管理cookie?

新的失败尝试:

现在每个线程都使用自己的索引,但它们似乎仍然会互相覆盖cookie。 有什么想法吗?

public class threadedCookieStore implements CookieStore, Runnable {
    CookieStore[] store = new CookieStore[1000];
    int index;

    public threadedCookieStore(int new_index) {
        index = new_index;
        // get the default in memory cookie store
        store[index] = new CookieManager().getCookieStore();


        // todo: read in cookies from persistant storage
        // and add them store

        // add a shutdown hook to write out the in memory cookies
        Runtime.getRuntime().addShutdownHook(new Thread(this)); 
    }

    public void run() {
        // todo: write cookies in store to persistent storage
    }

    public void add(URI uri, HttpCookie cookie) {
        store[index].add(uri, cookie);
    }

    public List<HttpCookie> get(URI uri) {
        return store[index].get(uri);
    }

    public List<HttpCookie> getCookies() {
        return store[index].getCookies();
    }

    public List<URI> getURIs() {
        return store[index].getURIs();
    }

    public boolean remove(URI uri, HttpCookie cookie) {
        return store[index].remove(uri, cookie);
    }

     public boolean removeAll()  {
         return store[index].removeAll();
    }
}

在这个类内:

threadedCookieStore cookiestore = new threadedCookieStore(index);

manager = new CookieManager(cookiestore,CookiePolicy.ACCEPT_ALL);
manager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
CookieHandler.setDefault(manager);

5
我无法相信java.net团队编写API的能力如此糟糕。为什么我们不能单独设置每个连接的Cookie管理器?:( 你会认为他们在AuthenticationHandler之后应该已经吸取了教训。 - Hakanai
更好的解决方案可以在这里找到。 - Constantinos
7个回答

14

谢谢大家。

我已经赞成了所有的答案,但是没有一个提供完整的解决方案。

由于在谷歌搜索这个问题会导致跳转到这个页面,所以我将发布完整的解决方案并接受自己的答案:

操作方法:

1 将CookieHandler扩展为SessionCookieManager

这基于如何在Java中使用HttpURLConnection和CookieManager为每个连接使用不同的cookie,nivs描述得很正确,但没有提供完整的解决方案。因此,大部分或全部功劳归于他,我只是完成了完整的操作方法。SessionCookieManager基于Java的源代码http://docs.oracle.com/javase/7/docs/api/java/net/CookieManager.html

import java.io.IOException;
import java.net.CookieHandler;
import java.net.CookiePolicy;
import java.net.CookieStore;
import java.net.HttpCookie;
import java.net.URI;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

public class SessionCookieManager extends CookieHandler
{
    private CookiePolicy policyCallback;


    public SessionCookieManager() {
        this(null, null);
    }

    private final static SessionCookieManager ms_instance = new SessionCookieManager();

    public static SessionCookieManager getInstance()
    {
        return ms_instance;
    }

    private final static ThreadLocal<CookieStore> ms_cookieJars = new ThreadLocal<CookieStore>() {
        @Override
        protected synchronized CookieStore initialValue() { return new InMemoryCookieStore(); }
    };

    public void clear()
    {
        getCookieStore().removeAll();
    }
    public SessionCookieManager(CookieStore store,
                         CookiePolicy cookiePolicy)
    {
        // use default cookie policy if not specify one
        policyCallback = (cookiePolicy == null) ? CookiePolicy.ACCEPT_ALL //note that I changed it to ACCEPT_ALL
                                                : cookiePolicy;

        // if not specify CookieStore to use, use default one

    }
    public void setCookiePolicy(CookiePolicy cookiePolicy) {
        if (cookiePolicy != null) policyCallback = cookiePolicy;
    }

    public CookieStore getCookieStore() {
        return ms_cookieJars.get();
    }

    public Map<String, List<String>>
        get(URI uri, Map<String, List<String>> requestHeaders)
        throws IOException
    {
        // pre-condition check
        if (uri == null || requestHeaders == null) {
            throw new IllegalArgumentException("Argument is null");
        }

        Map<String, List<String>> cookieMap =
                        new java.util.HashMap<String, List<String>>();
        // if there's no default CookieStore, no way for us to get any cookie
        if (getCookieStore() == null)
            return Collections.unmodifiableMap(cookieMap);

        List<HttpCookie> cookies = new java.util.ArrayList<HttpCookie>();
        for (HttpCookie cookie : getCookieStore().get(uri)) {
            // apply path-matches rule (RFC 2965 sec. 3.3.4)
            if (pathMatches(uri.getPath(), cookie.getPath())) {
                cookies.add(cookie);
            }
        }

        // apply sort rule (RFC 2965 sec. 3.3.4)
        List<String> cookieHeader = sortByPath(cookies);

        cookieMap.put("Cookie", cookieHeader);
        return Collections.unmodifiableMap(cookieMap);
    }


    public void
        put(URI uri, Map<String, List<String>> responseHeaders)
        throws IOException
    {
        // pre-condition check
        if (uri == null || responseHeaders == null) {
            throw new IllegalArgumentException("Argument is null");
        }


        // if there's no default CookieStore, no need to remember any cookie
        if (getCookieStore() == null)
            return;

        for (String headerKey : responseHeaders.keySet()) {
            // RFC 2965 3.2.2, key must be 'Set-Cookie2'
            // we also accept 'Set-Cookie' here for backward compatibility
            if (headerKey == null
                || !(headerKey.equalsIgnoreCase("Set-Cookie2")
                     || headerKey.equalsIgnoreCase("Set-Cookie")
                    )
                )
            {
                continue;
            }

            for (String headerValue : responseHeaders.get(headerKey)) {
                try {
                    List<HttpCookie> cookies = HttpCookie.parse(headerValue);
                    for (HttpCookie cookie : cookies) {
                        if (shouldAcceptInternal(uri, cookie)) {
                            getCookieStore().add(uri, cookie);
                        }
                    }
                } catch (IllegalArgumentException e) {
                    // invalid set-cookie header string
                    // no-op
                }
            }
        }
    }


    /* ---------------- Private operations -------------- */

    // to determine whether or not accept this cookie
    private boolean shouldAcceptInternal(URI uri, HttpCookie cookie) {
        try {
            return policyCallback.shouldAccept(uri, cookie);
        } catch (Exception ignored) { // pretect against malicious callback
            return false;
        }
    }


    /*
     * path-matches algorithm, as defined by RFC 2965
     */
    private boolean pathMatches(String path, String pathToMatchWith) {
        if (path == pathToMatchWith)
            return true;
        if (path == null || pathToMatchWith == null)
            return false;
        if (path.startsWith(pathToMatchWith))
            return true;

        return false;
    }


    /*
     * sort cookies with respect to their path: those with more specific Path attributes
     * precede those with less specific, as defined in RFC 2965 sec. 3.3.4
     */
    private List<String> sortByPath(List<HttpCookie> cookies) {
        Collections.sort(cookies, new CookiePathComparator());

        List<String> cookieHeader = new java.util.ArrayList<String>();
        for (HttpCookie cookie : cookies) {
            // Netscape cookie spec and RFC 2965 have different format of Cookie
            // header; RFC 2965 requires a leading $Version="1" string while Netscape
            // does not.
            // The workaround here is to add a $Version="1" string in advance
            if (cookies.indexOf(cookie) == 0 && cookie.getVersion() > 0) {
                cookieHeader.add("$Version=\"1\"");
            }

            cookieHeader.add(cookie.toString());
        }
        return cookieHeader;
    }


    static class CookiePathComparator implements Comparator<HttpCookie> {
        public int compare(HttpCookie c1, HttpCookie c2) {
            if (c1 == c2) return 0;
            if (c1 == null) return -1;
            if (c2 == null) return 1;

            // path rule only applies to the cookies with same name
            if (!c1.getName().equals(c2.getName())) return 0;

            // those with more specific Path attributes precede those with less specific
            if (c1.getPath().startsWith(c2.getPath()))
                return -1;
            else if (c2.getPath().startsWith(c1.getPath()))
                return 1;
            else
                return 0;
        }
    }
}
注意,在我的情况下,我将CookiePolicy的默认值更改为ACCEPT_ALL 全局范围内,在运行任何线程之前调用:
CookieHandler.setDefault(SessionCookieManager.getInstance());

3 当您的线程完成时,请在其中调用:

SessionCookieManager.getInstance().clear();

再次声明:这不是我的想法,只是将它们组合在一起。 非常感谢Javahttps://stackoverflow.com/users/1442259/nivs的贡献。


7

谢谢,我尝试使用您的答案,但它基于旧版本的CookieManager(可能是您必须使用ACCEPT_ALL的原因),并引用了包私有的InMemoryCookieStore,这启发了我找到了最终解决方案。之前应该对我们所有人都很明显:一个ThreadLocal CookieStore代理类。

CookieHandler.setDefault(new CookieManager(new ThreadLocalCookieStore(), null));

使用

import java.net.CookieManager;
import java.net.CookieStore;
import java.net.HttpCookie;
import java.net.URI;
import java.util.List;

public class ThreadLocalCookieStore implements CookieStore {

    private final static ThreadLocal<CookieStore> ms_cookieJars = new ThreadLocal<CookieStore>() {
        @Override
        protected synchronized CookieStore initialValue() {
            return (new CookieManager()).getCookieStore(); /*InMemoryCookieStore*/
            }
    };

    @Override
    public void add(URI uri, HttpCookie cookie) {
        ms_cookieJars.get().add(uri, cookie);
    }

    @Override
    public List<HttpCookie> get(URI uri) {
        return ms_cookieJars.get().get(uri);
    }

    @Override
    public List<HttpCookie> getCookies() {
        return ms_cookieJars.get().getCookies();
    }

    @Override
    public List<URI> getURIs() {
        return ms_cookieJars.get().getURIs();
    }

    @Override
    public boolean remove(URI uri, HttpCookie cookie) {
        return ms_cookieJars.get().remove(uri, cookie);
    }

    @Override
    public boolean removeAll() {
        return ms_cookieJars.get().removeAll();
    }
}

对我来说,似乎像魔法一样工作得很好


2

根据这个帖子中的答案,我创建了另一个非常简单的ThreadLocalCookieStore实现,并将其推送到GitHub(同时在那里提供它作为Maven依赖):

public class ThreadLocalCookieStore implements CookieStore {
    private final static ThreadLocal<CookieStore> stores = new ThreadLocal<CookieStore>() {
        @Override protected synchronized CookieStore initialValue() { 
            return (new CookieManager()).getCookieStore(); //InMemoryCookieStore 
        }
    };

    @Override public void add(URI uri, HttpCookie cookie) { getStore().add(uri,cookie); }
    @Override public List<HttpCookie> get(URI uri) { return getStore().get(uri); }
    @Override public List<HttpCookie> getCookies() { return getStore().getCookies(); }
    @Override public List<URI> getURIs() { return getStore().getURIs(); }
    @Override public boolean remove(URI uri, HttpCookie cookie) { return getStore().remove(uri,cookie); }
    @Override public boolean removeAll() { return getStore().removeAll(); }
    @Override public int hashCode() { return getStore().hashCode(); }

    protected CookieStore getStore() { return stores.get(); }
    public void purgeStore() { stores.remove(); }
}

设置 cookie 存储变得非常简单,只需少量代码即可完成,例如可以设置任何策略值:

CookieHandler.setDefault(new java.net.CookieManager(
    new ThreadLocalCookieStore(), CookiePolicy.ACCEPT_ALL));

此外,该依赖项还包括一个小的servlet @WebFilter,以在需要时在多个servlet请求之间分离cookie存储。

1
< p > < code > ThreadLocal CookieStore 由 DavidBlackledge 提供,我认为这是最好的方法。出于内存效率的考虑,我在此提供了一个普通 < code > CookieStore 的简单实现,因此您不必为每个线程(假设您有超过几个)实例化整个 < code > CookieManager 。< / p >
/**
 * @author lidor
 * A simple implementation of CookieStore
 */
public class CookieJar implements CookieStore {

private Map<URI, List<HttpCookie>> jar;
private List<HttpCookie> freeCookies;

public CookieJar() {
    jar = new HashMap<URI, List<HttpCookie>>();
    freeCookies = new ArrayList<HttpCookie>();
}

@Override
public void add(URI uri, HttpCookie cookie) {
    if (uri != null) {
        if (!jar.containsKey(uri))
            jar.put(uri, new ArrayList<HttpCookie>());
        List<HttpCookie> cookies = jar.get(uri);
        cookies.add(cookie);
    } else {
        freeCookies.add(cookie);
    }
}

@Override
public List<HttpCookie> get(URI uri) {
    Log.trace("CookieJar.get (" + this + ") called with URI " + uri + " (host=" + uri.getHost() + ")");
    List<HttpCookie> liveCookies = new ArrayList<HttpCookie>();
    if (jar.containsKey(uri)) {
        for (HttpCookie cookie : jar.get(uri)) {
            if (!cookie.hasExpired())
                liveCookies.add(cookie);
        }
    }
    for (HttpCookie cookie : getCookies()) {
        if (cookie.getDomain().equals(uri.getHost()))
            if (!liveCookies.contains(cookie))
                liveCookies.add(cookie);
    }
    return Collections.unmodifiableList(liveCookies);
}

@Override
public List<HttpCookie> getCookies() {
    List<HttpCookie> liveCookies = new ArrayList<HttpCookie>();
    for (URI uri : jar.keySet())
        for (HttpCookie cookie : jar.get(uri)) {
            if (!cookie.hasExpired())
                liveCookies.add(cookie);
        }
    for (HttpCookie cookie : freeCookies) {
        if (!cookie.hasExpired())
            liveCookies.add(cookie);
    }
    return Collections.unmodifiableList(liveCookies);
}

@Override
public List<URI> getURIs() {
    return Collections.unmodifiableList(new ArrayList<URI>(jar.keySet()));
}

@Override
public boolean remove(URI uri, HttpCookie cookie) {
    if (jar.containsKey(uri)) {
        return jar.get(uri).remove(cookie);
    } else {
        return freeCookies.remove(cookie);
    }
}

@Override
public boolean removeAll() {
    boolean ret = (jar.size() > 0) || (freeCookies.size() > 0);
    jar.clear();
    freeCookies.clear();
    return ret;
}
}

如果你有这个CookieJar,那么你可以将ms_cookieJars声明更改为以下内容:
private final static ThreadLocal<CookieStore> ms_cookieJars = new ThreadLocal<CookieStore>() {
        @Override
        protected synchronized CookieStore initialValue() { 
            return new CookieJar();
        }
    };

1
你可以安装一个CookieHandler来管理ThreadLocal CookieManager实例。

1
不知道从哪里开始。有任何例子吗? - Wurstbro
1
@Wurstbro - 创建一个CookieHandler,其中方法委托给在ThreadLocal中维护的CookieHandler实例。 - jtahlborn

0

如何使用ThreadLocal CookieManager?与其他答案的想法相同,但似乎需要更少的代码:

public class ThreadLocalCookies extends CookieManager {

    private static CookiePolicy s_policy = null;
    private static ThreadLocal<CookieManager> s_impl =
        new ThreadLocal<CookieManager>() {
            @Override protected CookieManager initialValue() {
                if (null == s_policy) {
                    throw new IllegalStateException("Call install() first");
                }
                return new CookieManager(null, s_policy);
            }
        };

    public static void install() {
        install(CookiePolicy.ACCEPT_ALL);
    }

    public static void install(CookiePolicy policy) {
        s_policy = policy;
        CookieHandler.setDefault(new ThreadLocalCookies());
    }

    public static void clear() {
        s_impl.set(new CookieManager(null, s_policy));
    }

    @Override
    public Map<String, List<String>>
    get(URI uri, Map<String, List<String>> requestHeaders) 
    throws IOException {
        return s_impl.get().get(uri, requestHeaders);
    }

    @Override
    public void put(URI uri, Map<String,List<String>> responseHeaders)
    throws IOException {
        s_impl.get().put(uri, responseHeaders);
    }
}

0

听起来很有前途。由于我没有使用单个Cookies,那么我该在哪里设置它呢? - Wurstbro
不要使用默认的CookieManager构造函数,而是使用接受CookieStore的构造函数。您可以定义自己的CookieStore来实现这一点。CookieManager javadoc提供了详细信息。http://docs.oracle.com/javase/6/docs/api/java/net/CookieStore.html - dkaustubh
CookieStore是一个接口。您需要实现并拥有自己的CookieStore。 - dkaustubh
请查看我问题的编辑。我尝试使用您的建议,但无法使其工作。 - Wurstbro
1
我进行了点踩,因为setPath方法似乎是用于设置实际cookie的域和路径,而不是Java中持久化cookie的位置。所以如果有两个线程都获取相同的URL,每个具有相同路径的cookie仍然会被覆盖。 - georgiecasey
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接