可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am trying to make multiple connections via threads.
But every connection seems to override the other's cookies, resulting in the connections using the wrong cookies.
inside the threaded class's constructor:
manager = new CookieManager(); manager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); CookieHandler.setDefault(manager);
Any way to manage the cookies per thread or per class?
New failed try:
Now every thread is using it's own index, yet they still seem to override each other cookie-wise. Any ideas?
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 get(URI uri) { return store[index].get(uri); } public List getCookies() { return store[index].getCookies(); } public List 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(); } }
Within the class:
threadedCookieStore cookiestore = new threadedCookieStore(index); manager = new CookieManager(cookiestore,CookiePolicy.ACCEPT_ALL); manager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); CookieHandler.setDefault(manager);
回答1:
Thanks everyone.
I upvoted all the answers, yet none had a complete solution.
Since google'ing this problem leads to this page here, I'll post the complete solution and accept my own answer:
HowTo:
1 Extend CookieHandler
to SessionCookieManager
this is based on How to use different cookies for each connection using HttpURLConnection and the CookieManager in Java , nivs describes it correctly, doesn't provide a full solution tho. So most/all credit goes to him, I'm just making the complete HowTo. The SessionCookieManager is based on Java
's source code 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 ms_cookieJars = new ThreadLocal() { @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> get(URI uri, Map> requestHeaders) throws IOException { // pre-condition check if (uri == null || requestHeaders == null) { throw new IllegalArgumentException("Argument is null"); } Map> cookieMap = new java.util.HashMap>(); // if there's no default CookieStore, no way for us to get any cookie if (getCookieStore() == null) return Collections.unmodifiableMap(cookieMap); List cookies = new java.util.ArrayList(); 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 cookieHeader = sortByPath(cookies); cookieMap.put("Cookie", cookieHeader); return Collections.unmodifiableMap(cookieMap); } public void put(URI uri, Map> 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 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 sortByPath(List cookies) { Collections.sort(cookies, new CookiePathComparator()); List cookieHeader = new java.util.ArrayList(); 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 { 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; } } }
Note that in my case I changed the default value of CookiePolicy
to ACCEPT_ALL
2 In global scope, before running any threads, call:
CookieHandler.setDefault(SessionCookieManager.getInstance());
3 When your thread is finished, call inside of it:
SessionCookieManager.getInstance().clear();
again: not my idea, just putting it together. All credit goes to Java
and https://stackoverflow.com/users/1442259/nivs
回答2:
Thanks, I tried to use your answer, but it was based on an old version of CookieManager (probably why you had to use ACCEPT_ALL) and referenced the package-private InMemoryCookieStore so it inspired me to the final solution. Should have been obvious to all of us before: a ThreadLocal CookieStore proxy class.
CookieHandler.setDefault(new CookieManager(new ThreadLocalCookieStore(), null));
with
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 ms_cookieJars = new ThreadLocal() { @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 get(URI uri) { return ms_cookieJars.get().get(uri); } @Override public List getCookies() { return ms_cookieJars.get().getCookies(); } @Override public List 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(); } }
Seems to be working like a charm for me
回答3:
You could install a CookieHandler which manages ThreadLocal CookieManager instances.
回答4:
The ThreadLocal
CookieStore
by DavidBlackledge is imo the best way to go. For the sake of memory efficiency I'm providing here a simple implementation of a regular CookieStore
so you don't have to instantiate a whole CookieManager
for each thread (assuming you have more than just a few).
/** * @author lidor * A simple implementation of CookieStore */ public class CookieJar implements CookieStore { private Map> jar; private List freeCookies; public CookieJar() { jar = new HashMap>(); freeCookies = new ArrayList(); } @Override public void add(URI uri, HttpCookie cookie) { if (uri != null) { if (!jar.containsKey(uri)) jar.put(uri, new ArrayList()); List cookies = jar.get(uri); cookies.add(cookie); } else { freeCookies.add(cookie); } } @Override public List get(URI uri) { Log.trace("CookieJar.get (" + this + ") called with URI " + uri + " (host=" + uri.getHost() + ")"); List liveCookies = new ArrayList(); 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 getCookies() { List liveCookies = new ArrayList(); 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 getURIs() { return Collections.unmodifiableList(new ArrayList(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; } }
So if you have this CookieJar then you can change the ms_cookieJars
declaration to this:
private final static ThreadLocal ms_cookieJars = new ThreadLocal() { @Override protected synchronized CookieStore initialValue() { return new CookieJar(); } };
回答5:
Based on the answers in this thread, I created another very simple ThreadLocalCookieStore
implementation and pushed it to GitHub (also providing it as a Maven dependency there):
public class ThreadLocalCookieStore implements CookieStore { private final static ThreadLocal stores = new ThreadLocal() { @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 get(URI uri) { return getStore().get(uri); } @Override public List getCookies() { return getStore().getCookies(); } @Override public List 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(); } }
Without much code it becomes very simple to set the cookie store, with any policy value, e.g.:
CookieHandler.setDefault(new java.net.CookieManager( new ThreadLocalCookieStore(), CookiePolicy.ACCEPT_ALL));
In addition the dependency features a small sevlet @WebFilter
, to separate cookie stores on multiple serlvet requests if required.
回答6:
回答7:
How about a ThreadLocal CookieManager? Same idea as some of the other answers but seems to require less code:
public class ThreadLocalCookies extends CookieManager { private static CookiePolicy s_policy = null; private static ThreadLocal s_impl = new ThreadLocal() { @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> get(URI uri, Map> requestHeaders) throws IOException { return s_impl.get().get(uri, requestHeaders); } @Override public void put(URI uri, Map> responseHeaders) throws IOException { s_impl.get().put(uri, responseHeaders); } }