在上一文中,我们提到retrofit 2.0中如何实现非持久化cookie的两种方案,但并未做过深的解释。
现在我们重点关注JavaNetCookieJar实现非持久化cookie背后的原理。
话不多说,步入正题。
非持久化Cookie实现分析
首先来看上文中提到的非持久化cookie的实现:
public void setCookies(OkHttpClient.Builder builder) {
CookieManager cookieManager = new CookieManager();
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
builder.cookieJar(new JavaNetCookieJar(cookieManager));
}
现在我们就以这段代码为起点来研究为什么这几行代码就实现了非持久化cookie呢?不难先发现此处实现cookie的关键就是cookieJar()
,我们就以该方法切入。
首先来看该方法的源码:
//设置cookie处理器,如果没设置,则使用CookieJar.NO_COOKIES作为默认的处理器
public Builder cookieJar(CookieJar cookieJar) {
if (cookieJar == null) throw new NullPointerException("cookieJar == null");
this.cookieJar = cookieJar;
return this;
}
通过该方法我们知道这里的关键就在于CookieJar接口,来看看这个接口的定义:
public interface CookieJar {
//默认的cookie处理器,不接受任何cookie
CookieJar NO_COOKIES = new CookieJar() {
@Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
}
@Override public List<Cookie> loadForRequest(HttpUrl url) {
return Collections.emptyList();
}
};
//根绝cookie处理策略,保存响应中的cookie
void saveFromResponse(HttpUrl url, List<Cookie> cookies);
//为请求添加cookie
List<Cookie> loadForRequest(HttpUrl url);
}
该接口非常简单,提供了保存和加载cookie的两个方法loadForRequest(HttpUrl url)
及saveFromResponse(HttpUrl url,List<Cookie> cookies)
,这就意味这我们可以自行实现接口来实现对cookie管理。到这里想必各位可能已经在想“我们自行实现该接口来将cookie保存到本地,使用的时候再从本地读取,这样不久实现cookie持久化了么?”,这当然没问题,但我们先继续往下看。
除此之外,该接口中存在一个默认的实现NO_COOKIES。
接下来,我们来看看该接口的另外一个实现类JavaNetCookieJar,它本质上只是java.net.CookieHandler的代理类。同样,来看一下JavaNetCookieJar中的源码:
public final class JavaNetCookieJar implements CookieJar {
private final CookieHandler cookieHandler;
public JavaNetCookieJar(CookieHandler cookieHandler) {
this.cookieHandler = cookieHandler;
}
@Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
if (cookieHandler != null) {
List<String> cookieStrings = new ArrayList<>();
for (Cookie cookie : cookies) {
//遍历cookie集合
cookieStrings.add(cookie.toString());
}
Map<String, List<String>> multimap = Collections.singletonMap("Set-Cookie", cookieStrings);
try {
//具体的保存工作交给cookieHandler去处理
cookieHandler.put(url.uri(), multimap);
} catch (IOException e) {
Internal.logger.log(WARNING, "Saving cookies failed for " + url.resolve("/..."), e);
}
}
}
@Override public List<Cookie> loadForRequest(HttpUrl url) {
// The RI passes all headers. We don't have 'em, so we don't pass 'em!
Map<String, List<String>> headers = Collections.emptyMap();
Map<String, List<String>> cookieHeaders;
try {
//从cookieHandler中取出cookie集合。
cookieHeaders = cookieHandler.get(url.uri(), headers);
} catch (IOException e) {
Internal.logger.log(WARNING, "Loading cookies failed for " + url.resolve("/..."), e);
return Collections.emptyList();
}
List<Cookie> cookies = null;
for (Map.Entry<String, List<String>> entry : cookieHeaders.entrySet()) {
String key = entry.getKey();
if (("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key))
&& !entry.getValue().isEmpty()) {
for (String header : entry.getValue()) {
if (cookies == null) cookies = new ArrayList<>();
cookies.addAll(decodeHeaderAsJavaNetCookies(url, header));
}
}
}
return cookies != null
? Collections.unmodifiableList(cookies)
: Collections.<Cookie>emptyList();
}
//将请求Header转为OkHttp中HttpCookie
private List<Cookie> decodeHeaderAsJavaNetCookies(HttpUrl url, String header) {
List<Cookie> result = new ArrayList<>();
for (int pos = 0, limit = header.length(), pairEnd; pos < limit; pos = pairEnd + 1) {
//具体转换过程在此不做展示
}
return result;
}
}
上面的代码非常简单,其核心无非在于通过CookieHandler实现对cookie的保存和取值。既然,真正的工作类是CookieHandler,那我们就重点关注CookieHandler.
public abstract class CookieHandler {
private static CookieHandler cookieHandler;
public synchronized static CookieHandler getDefault() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(SecurityConstants.GET_COOKIEHANDLER_PERMISSION);
}
return cookieHandler;
}
public synchronized static void setDefault(CookieHandler cHandler) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(SecurityConstants.SET_COOKIEHANDLER_PERMISSION);
}
cookieHandler = cHandler;
}
public abstract Map<String, List<String>>
get(URI uri, Map<String, List<String>> requestHeaders)
throws IOException;
public