在上一文中,我们提到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 abstract void
put(URI uri, Map<String, List<String>> responseHeaders)
throws IOException;
}
我们发现CookieHandler是个抽象类,其实现类就是我们上面在非持久化Cookie中用到的CookieManager,没办法,我们在此转移焦点至CookieManager:
public class CookieManager extends CookieHandler
{
private CookiePolicy policyCallback;//cookie处理策略
private CookieStore cookieJar = null;//cookie存储
public CookieManager() {
this(null, null);
}
//在这里我们可以指定cookie存储方式及cookie处理策略
public CookieManager(CookieStore store,
CookiePolicy cookiePolicy)
{
// use default cookie policy if not specify one
policyCallback = (cookiePolicy == null) ? CookiePolicy.ACCEPT_ORIGINAL_SERVER
: cookiePolicy;
// 如果没有指定持久化方式,则默认使用内存持久化。
if (store == null) {
cookieJar = new InMemoryCookieStore();
} else {
cookieJar = store;
}
}
//指定cookie处理策略
public void setCookiePolicy(CookiePolicy cookiePolicy) {
if (cookiePolicy != null) policyCallback = cookiePolicy;
}
public CookieStore getCookieStore() {
return cookieJar;
}
//获取cookie
public Map<String, List<String>>
get(URI uri, Map<String, List<String>> requestHeaders)
throws IOException
{
//... 省略多行代码
Map<String, List<String>> cookieMap =
new java.util.HashMap<String, List<String>>();
//... 省略多行代码
for (HttpCookie cookie : cookieJar.get(uri)) {
//... 省略多行代码
}
// apply sort rule (RFC 2965 sec. 3.3.4)
List<String> cookieHeader = sortByPath(cookies);
cookieMap.put("Cookie", cookieHeader);
return Collections.unmodifiableMap(cookieMap);
}
//保存cookie
public void
put(URI uri, Map<String, List<String>> responseHeaders)
throws IOException
{
//... 省略多行代码
PlatformLogger logger = PlatformLogger.getLogger("java.net.CookieManager");
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;
try {
cookies = HttpCookie.parse(headerValue);
} catch (IllegalArgumentException e) {
// Bogus header, make an empty list and log the error
cookies = java.util.Collections.emptyList();
if (logger.isLoggable(PlatformLogger.Level.SEVERE)) {
logger.severe("Invalid cookie for " + uri + ": " + headerValue);
}
}
for (HttpCookie cookie : cookies) {
if (cookie.getPath() == null) {
//... 省略多行代码
cookie.setPath(path);
}
if (cookie.getDomain() == null) {
//... 省略多行代码
cookie.setDomain(host);
}
//... 省略多行代码
if (shouldAcceptInternal(uri, cookie)) {
cookieJar.add(uri, cookie);
}
}
} catch (IllegalArgumentException e) {
// invalid set-cookie header string
// no-op
}
}
}
}
}
在CacheManager中,需要重点关注的便是policyCallback和cookieJar两个成员变量。分别来看看这二者做了什么?
cookie处理策略
cookie的处理策略由CookiePolicy接口确定,该接口中预置了三种处理策略:ACCEPT_ALL,ACCEPT_NONE及ACCEPT_SERVER,分别用于接受所有cookie,不接受cookie,只接受初始服务器的cookie。
cookie存储
cookie的存储则由CookieStore接口负责,该接口目前存在唯一的实现类InMemoryCookieStore,通过该实现类的名字也大概猜的出它将cookie存在内存中了,同样我们简单的浏览一下其源码:
class InMemoryCookieStore implements CookieStore {
private List<HttpCookie> cookieJar = null;
private Map<String, List<HttpCookie>> domainIndex = null;
private Map<URI, List<HttpCookie>> uriIndex = null;
// use ReentrantLock instead of syncronized for scalability
private ReentrantLock lock = null;
/**
* The default ctor
*/
public InMemoryCookieStore() {
cookieJar = new ArrayList<HttpCookie>();
domainIndex = new HashMap<String, List<HttpCookie>>();
uriIndex = new HashMap<URI, List<HttpCookie>>();
lock = new ReentrantLock(false);
}
//保存cookie
public void add(URI uri, HttpCookie cookie) {
// pre-condition : argument can't be null
if (cookie == null) {
throw new NullPointerException("cookie is null");
}
lock.lock();
try {
// 首先移除已经存在的cookie
cookieJar.remove(cookie);
// 缓存时间不为0
if (cookie.getMaxAge() != 0) {
cookieJar.add(cookie);
// 保存到域索引中
if (cookie.getDomain() != null) {
addIndex(domainIndex, cookie.getDomain(), cookie);
}
//保存到url索引中
if (uri != null) {
// add it to uri index, too
addIndex(uriIndex, getEffectiveURI(uri), cookie);
}
}
} finally {
lock.unlock();
}
}
}
到现在我们已经弄明白非持久化Cookie的原理,这里用一张类图来概括一下:
从设计的来说,这里也充分体现了面向抽象编程,单一职责理念等。对这些设计原则不明白的可以看我以前写的文章。
持久化Cookie实现
自定义InDiskCookieStore实现持久化Cookie
通过上面的分析,我们最终知道非持久化Cookie的存储靠InMemoryCookieStore实现,除此之外,我们发现CookieManager中也可以指定另外的CookieStore的实现。这样看来,我们只需要自定义InDiskCookieStore实现CookieStore,并将其设置给CookieManager就可以实现Cookie的持久化了。接下来,我们仿照inMemoryCookieStore来实现自己的InDiskCookieStore。
(稍后补码)
自定义拦截器实现持久化Cookie
通过自定义拦截器实现Cookie持久化的思路我们已经在上一篇文章说过,就不多做解释了,直接来看代码即可:
(稍后补码)
自定义CookieJar实现持久化Cookie
自定义CookieJar中对Cookie的保存和加载实现和自定义拦截器的方式一致,故不做详细的说明了。后面直接上代码。
总结
到现在我们彻底的了解Cookie的持久化技术,不难发现这三种持久化实现方案原理都是大同小异。