okhttp原理解析之cookie
建议先对HTTP有个大概的了解:HTTP概述
okhttp原理解析之整体流程
okhttp原理解析之cookie
okhttp 缓存解析
[okhttp Dns解析](# 待续)
[okhttp 连接解析](# 待续)
[okhttp http解析](# 待续)
[okhttp https解析](# 待续)
[okhttp io解析](# 待续)
桥链拦截器源码解析
在okhttp原理解析之整体流程的解析中,cookie是责任链中第二个执行的链条,其是在BridgeInterceptor(桥链拦截器)中实现的,我们先看看BridgeInterceptor的源码:
public final class BridgeInterceptor implements Interceptor {
//HTTP Cookie处理器:cookie存储等
private final CookieJar cookieJar;
public BridgeInterceptor(CookieJar cookieJar) {
this.cookieJar = cookieJar;
}
//桥链拦截器的责任
//chain:下一个责任
public Response intercept(Chain chain) throws IOException {
//请求内容对象
Request userRequest = chain.request();
//重构请求内容对象的buider
Builder requestBuilder = userRequest.newBuilder();
//请求内容体体
RequestBody body = userRequest.body();
if (body != null) {
//如果请求内容体不为空
//请求内容体的媒体类型,如:text/html、image/png等
MediaType contentType = body.contentType();
if (contentType != null) {
//如果请求内容体的媒体类型不为空
//设置HTTP头部媒体类型
requestBuilder.header("Content-Type", contentType.toString());
}
///请求内容体的内容长度,单位字节
long contentLength = body.contentLength();
if (contentLength != -1L) {
//如果请求内容体的内容长度不为-1,表示是整体传输
//设置HTTP头部的Content-Length(内容长度)
requestBuilder.header("Content-Length", Long.toString(contentLength));
//删除HTTP头部的Transfer-Encoding(传输编码:逐跳传输内容,每一端可以使用不同的编码,由于此分支是整体传输,故不需要逐跳)
requestBuilder.removeHeader("Transfer-Encoding");
} else {
//如果请求内容体的内容长度 == -1,表示分段传输
//设置HTTP头部的Transfer-Encoding(传输编码:逐跳传输内容,每一端可以使用不同的编码,由于此分支是逐跳传输)
requestBuilder.header("Transfer-Encoding", "chunked");
//删除HTTP头部的Content-Length(内容长度,因为不是整体传输,所以内容长度不明确)
requestBuilder.removeHeader("Content-Length");
}
}
if (userRequest.header("Host") == null) {
//设置HTTP头部的Host字段
requestBuilder.header("Host", Util.hostHeader(userRequest.url(), false));
}
if (userRequest.header("Connection") == null) {
//设置HTTP头部的Connection字段(连接类型,使用长连接)
requestBuilder.header("Connection", "Keep-Alive");
}
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
//设置默认的压缩算法为gzip
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
//从本地加载本次请求路径对应的cookie列表
List<Cookie> cookies = this.cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
//如果cookie列表存在
//将cookie列表转变为格式化(key=val;key=val....)的字符串,并赋值给HTTP头部的Cookie字段
requestBuilder.header("Cookie", this.cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) {
//设置默认的HTTP头部User-Agent
requestBuilder.header("User-Agent", Version.userAgent());
}
//下一责任执行任务,以完成HTTP请求,最终返回HTTP响应对象
Response networkResponse = chain.proceed(requestBuilder.build());
//从响应对象的头部中读取Set-Cookie得到cookie列表,并进行保存
HttpHeaders.receiveHeaders(this.cookieJar, userRequest.url(), networkResponse.headers());
//重新构建响应Builder
okhttp3.Response.Builder responseBuilder = networkResponse.newBuilder().request(userRequest);
if (transparentGzip && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding")) && HttpHeaders.hasBody(networkResponse)) {
//如果响应内容体是gzip压缩过的
//构建压缩资源对象
GzipSource responseBody = new GzipSource(networkResponse.body().source());
//重构响应头部:移除Content-Encoding和Content-Length
Headers strippedHeaders = networkResponse.headers().newBuilder().removeAll("Content-Encoding").removeAll("Content-Length").build();
//设置响应头部为重构的头部
responseBuilder.headers(strippedHeaders);
//获取响应内容体的媒体类型
String contentType = networkResponse.header("Content-Type");
//解压缩响应内容,并赋值给重构的响应builder
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
//使用重构的响应builder构建新的响应对象,并返回
return responseBuilder.build();
}
//将cookie列表,格式化(key=val;key=val....)为cookie字符串
private String cookieHeader(List<Cookie> cookies) {
StringBuilder cookieHeader = new StringBuilder();
int i = 0;
for(int size = cookies.size(); i < size; ++i) {
if (i > 0) {
cookieHeader.append("; ");
}
Cookie cookie = (Cookie)cookies.get(i);
cookieHeader.append(cookie.name()).append('=').append(cookie.value());
}
return cookieHeader.toString();
}
}
桥链拦截器的主要职责是:
- 请求HTTP前,重新修正请求内容体的编码、压缩方式等
- 请求HTTP前,读取本次请求路径对应的cookie并设置到HTTP请求头部的cookie字段
- 请求HTTP后,得到响应对象,读取响应对象头部的的cookie列表并进行缓存
- 请求HTTP后,得到响应对象,如果响应体内容是经过压缩的,则进行解压缩处理
请求HTTP前,桥链拦截器对从本地中读取Cookie是通过cookieJar 来实现的,而okhttp默认是NO_COOKIES(其读取和缓存都是空函数,不做任何处理),所以cookieJar.loadForRequest返回的是空。我看看NO_COOKIES的实现代码:
CookieJar NO_COOKIES = new CookieJar() {
@Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
//将cookie列表保存到本地,不做任何处理
}
//从本地读取cookie列表
@Override public List<Cookie> loadForRequest(HttpUrl url) {
//返回空列表
return Collections.emptyList();
}
};
请求HTTP后,得到响应对象,使用HttpHeaders.receiveHeaders方法从响应对象中的头部Set-Cookie获取cookie列表,再调用cookieJar将cookie列表存储在本地,okhttp默认是NO_COOKIES(其读取和缓存都是空函数,不做任何处理),所以cookieJar.saveFromResponse是不会做任何处理的,我们来看看HttpHeaders.receiveHeaders的代码:
public static void receiveHeaders(CookieJar cookieJar, HttpUrl url, Headers headers) {
//如果cookie存取实现类是NO_COOKIES,则不做任何处理,直接返回
if (cookieJar == CookieJar.NO_COOKIES) return;
//从头部的Set-Cookie中读取并转义为Cookie列表
List<Cookie> cookies = Cookie.parseAll(url, headers);
if (cookies.isEmpty()) return;
//调用saveFromResponse将cookie列表进行存储
cookieJar.saveFromResponse(url, cookies);
}
HttpHeaders.receiveHeaders先使用Cookie.parseAll解析set-cookie列表转为cookie对象列表,继而用cookieJar.saveFromResponse进行持久化存储到本地中。
我们看看Cookie.parseAll是怎么将set-cookie列表转为cookie对象列表的:
public static List<Cookie> parseAll(HttpUrl url, Headers headers) {
//读取Set-Cookie字符串列表,每个Set-Cookie格式为:Set-Cookie: key=value[;Expires=Date Time][;Max-Age=second][;Secure][;HttpOnly][;SameSize=xxx][;Domain=xxx][;Path=xxx]
List<String> cookieStrings = headers.values("Set-Cookie");
List<Cookie> cookies = null;
for (int i = 0, size = cookieStrings.size(); i < size; i++) {
//遍历Set-Cookie字符串列表
//将字符串按格式转为为Cookie对象
Cookie cookie = Cookie.parse(url, cookieStrings.get(i));
if (cookie == null) continue;
if (cookies == null) cookies = new ArrayList<>();
cookies.add(cookie);
}
return cookies != null
? Collections.unmodifiableList(cookies)
: Collections.<Cookie>emptyList();
}
Cookie.parseAll先从响应头中读取set-cookie字符串列表,再遍历字符串列表,使用Cookie.parse将每个set-cookie字符串按照规范格式解析为Cookie对象。
我们接着看Cookie.parse怎么按规范格式将set-cookie字符串解析为Cookie对象:
//set-cookie字符串值格式转为Cookie对象
public static @Nullable Cookie parse(HttpUrl url, String setCookie) {
return parse(System.currentTimeMillis(), url, setCookie);
}
//set-cookie字符串值格式转为Cookie对象
//currentTimeMillis:当前处理时间
//url:cookie对应的url
//set-cookie字符串格式
static @Nullable Cookie parse(long currentTimeMillis, HttpUrl url, String setCookie) {
//字符串读取开始位置
int pos = 0;
//set-cookie字符串值总长度
int limit = setCookie.length();
//从pos-limit 获取第一个“;”的位置,这个位置前面是key=value
int cookiePairEnd = delimiterOffset(setCookie, pos, limit, ';');
//从pos-cookiePairEnd得到key=value中=的位置
int pairEqualsSign = delimiterOffset(setCookie, pos, cookiePairEnd, '=');
//如果pairEqualsSign ==cookiePairEnd,表示没有cookie键值对,立即返回空
if (pairEqualsSign == cookiePairEnd) return null;
//从pos-pairEqualsSign,即key=value中=的前面获取cookie的名称
String cookieName = trimSubstring(setCookie, pos, pairEqualsSign);
//如果cookie名称是空的或者含非法字符(\n、空字符串、非Ascii等),直接返回空
if (cookieName.isEmpty() || indexOfControlOrNonAscii(cookieName) != -1) return null;
//从pairEqualsSign+1到cookiePairEnd,即key=value中=的后面获取cookie的值
String cookieValue = trimSubstring(setCookie, pairEqualsSign + 1, cookiePairEnd);
//如果cookie的值是空的或者含非法字符(\n、空字符串、非Ascii等),直接返回空
if (indexOfControlOrNonAscii(cookieValue) != -1) return null;
//cookie过期时间点,单位秒
long expiresAt = HttpDate.MAX_DATE;
//过期时间比较值
long deltaSeconds = -1L;
//cookie中设置的域名
String domain = null;
//cookie中设置的path
String path = null;
//cookie中是否仅仅https有效
boolean secureOnly = false;
//cookie中是否JavaScript的Document.cookie无法访问带有HttpOnly的Cookie的值
boolean httpOnly = false;
//cookie是否仅主域名有效
boolean hostOnly = true;
//cookie是否持久化
boolean persistent = false;
//将pos设置为key=value;之后的位置
pos = cookiePairEnd + 1;
while (pos < limit) {
//遍历set-cookie值字符串,每次取“;”前面的name=value
int attributePairEnd = delimiterOffset(setCookie, pos, limit, ';');
int attributeEqualsSign = delimiterOffset(setCookie, pos, attributePairEnd, '=');
//取得属性名
String attributeName = trimSubstring(setCookie, pos, attributeEqualsSign);
//取得属性值
String attributeValue = attributeEqualsSign < attributePairEnd
? trimSubstring(setCookie, attributeEqualsSign + 1, attributePairEnd)
: "";
if (attributeName.equalsIgnoreCase("expires")) {
//如果属性名是expires,即过期时间点
try {
//将过期时间点转为毫米数
expiresAt = parseExpires(attributeValue, 0, attributeValue.length());
//需持久化
persistent = true;
} catch (IllegalArgumentException e) {
// Ignore this attribute, it isn't recognizable as a date.
}
} else if (attributeName.equalsIgnoreCase("max-age")) {
//如果属性名是max-age,即距离过期时间剩余多少秒
try {
//过期剩余秒数
deltaSeconds = parseMaxAge(attributeValue);
//需持久化
persistent = true;
} catch (NumberFormatException e) {
// Ignore this attribute, it isn't recognizable as a max age.
}
} else if (attributeName.equalsIgnoreCase("domain")) {
//如果属性名是domain,即cookie有效的域名
try {
//解析得到域名
domain = parseDomain(attributeValue);
//主域名和子域名都有效
hostOnly = false;
} catch (IllegalArgumentException e) {
// Ignore this attribute, it isn't recognizable as a domain.
}
} else if (attributeName.equalsIgnoreCase("path")) {
//如果属性名是path,即cookie有效的路径
path = attributeValue;
} else if (attributeName.equalsIgnoreCase("secure")) {
//如果属性名是secure,即cookie仅对https有效
secureOnly = true;
} else if (attributeName.equalsIgnoreCase("httponly")) {
//如果属性名是httponly,即JavaScript的Document.cookie无法访问带有HttpOnly的Cookie的值
httpOnly = true;
}
pos = attributePairEnd + 1;
}
// If 'Max-Age' is present, it takes precedence over 'Expires', regardless of the order the two
// attributes are declared in the cookie string.
//将剩余过期时间秒数转为具体过期时间点
if (deltaSeconds == Long.MIN_VALUE) {
expiresAt = Long.MIN_VALUE;
} else if (deltaSeconds != -1L) {
long deltaMilliseconds = deltaSeconds <= (Long.MAX_VALUE / 1000)
? deltaSeconds * 1000
: Long.MAX_VALUE;
expiresAt = currentTimeMillis + deltaMilliseconds;
if (expiresAt < currentTimeMillis || expiresAt > HttpDate.MAX_DATE) {
expiresAt = HttpDate.MAX_DATE; // Handle overflow & limit the date range.
}
}
// If the domain is present, it must domain match. Otherwise we have a host-only cookie.
String urlHost = url.host();
if (domain == null) {
//set-cookie中没有domain,从url中获取
domain = urlHost;
} else if (!domainMatch(urlHost, domain)) {
//set-cookie中的域名和url的域名不一致,表示从cookie是无效的,直接返回
return null; // No domain match? This is either incompetence or malice!
}
// If the domain is a suffix of the url host, it must not be a public suffix.
if (urlHost.length() != domain.length()
&& PublicSuffixDatabase.get().getEffectiveTldPlusOne(domain) == null) {
//校验set-cookie的域名如果是子域名,他必须是不是公共的子域名
return null;
}
// If the path is absent or didn't start with '/', use the default path. It's a string like
// '/foo/bar' for a URL like 'http://example.com/foo/bar/baz'. It always starts with '/'.
if (path == null || !path.startsWith("/")) {
String encodedPath = url.encodedPath();
int lastSlash = encodedPath.lastIndexOf('/');
path = lastSlash != 0 ? encodedPath.substring(0, lastSlash) : "/";
}
//将解析的的cookie属性值,构建Cookie对象
return new Cookie(cookieName, cookieValue, expiresAt, domain, path, secureOnly, httpOnly,
hostOnly, persistent);
}
set-cookie的规范格式:Set-Cookie: key=value[;Expires=Date Time][;Max-Age=second][;Secure][;HttpOnly][;SameSize=xxx][;Domain=xxx][;Path=xxx],所以Cookie.parse就是每次截取name=value;进行解析得到对应的cookie属性值,最后用这些属性值构建Cookie对象。
Cookie存取实现原理
在上述的源码解析中,cookie的本地读取和缓存均是经过CookieJar实现,而CookieJar只是一个接口,默认情况下Okhttp的cookie是一个CookieJar的内部类NO_COOKIES(其读取和缓存都是空函数,不做任何处理),即不使用cookie。鉴于此,我们借助第三方cookie实现库PersistentCookieJar进行讲解cookie是如何实现存取的。
public class PersistentCookieJar implements ClearableCookieJar {
//内存中缓存的cookie列表
private CookieCache cache;
//cookie持久化实现类(保存到本地文件)
private CookiePersistor persistor;
public PersistentCookieJar(CookieCache cache, CookiePersistor persistor) {
this.cache = cache;
this.persistor = persistor;
//从本地加载cookie列表,并缓存在内存中
this.cache.addAll(persistor.loadAll());
}
//保存响应中的cookie
@Override
synchronized public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
//将cookie列表缓存到内存中
cache.addAll(cookies);
//保存到文件中
persistor.saveAll(filterPersistentCookies(cookies));
}
//过滤不需要保存的cookie
private static List<Cookie> filterPersistentCookies(List<Cookie> cookies) {
List<Cookie> persistentCookies = new ArrayList<>();
for (Cookie cookie : cookies) {
//变量cookie列表
if (cookie.persistent()) {
//如果cookie需要保存,则加入持久化列表中,等待持久化保存
persistentCookies.add(cookie);
}
}
return persistentCookies;
}
//跟url加载对应的cookie列表
@Override
synchronized public List<Cookie> loadForRequest(HttpUrl url) {
List<Cookie> cookiesToRemove = new ArrayList<>();
List<Cookie> validCookies = new ArrayList<>();
for (Iterator<Cookie> it = cache.iterator(); it.hasNext(); ) {
//变量内存中的cookie列表
Cookie currentCookie = it.next();
if (isCookieExpired(currentCookie)) {
//如果cookie已过期
//将cookie加入待删除列表中
cookiesToRemove.add(currentCookie);
//从内存中移除过期的cookie
it.remove();
} else if (currentCookie.matches(url)) {
//如果cookie的路径和加载的路径匹配
//将cookie
validCookies.add(currentCookie);
}
}
//从持久化中删除待删除的cookie列表
persistor.removeAll(cookiesToRemove);
//返回匹配的cookie列表
return validCookies;
}
//判断cookie是否过期
private static boolean isCookieExpired(Cookie cookie) {
return cookie.expiresAt() < System.currentTimeMillis();
}
//清除内存中的cookie列表,并重新从持久化(文件)中加载到内存中
@Override
synchronized public void clearSession() {
cache.clear();
cache.addAll(persistor.loadAll());
}
//从内存和持久化中删除所有cookie
@Override
synchronized public void clear() {
cache.clear();
persistor.clear();
}
}
PersistentCookieJar 是cookie加载、保存、删除的入口,真正的实现是CookiePersistor,CookiePersistor的实现类是SharedPrefsCookiePersistor ,我看看SharedPrefsCookiePersistor 源码:
public class SharedPrefsCookiePersistor implements CookiePersistor {
//采用android的SharedPreferences 实现存储cookie
private final SharedPreferences sharedPreferences;
public SharedPrefsCookiePersistor(Context context) {
//初始化sharedPreferences实例
this(context.getSharedPreferences("CookiePersistence", Context.MODE_PRIVATE));
}
public SharedPrefsCookiePersistor(SharedPreferences sharedPreferences) {
//初始化sharedPreferences实例
this.sharedPreferences = sharedPreferences;
}
//从sharedPreferences 中加载所有的key-value
@Override
public List<Cookie> loadAll() {
List<Cookie> cookies = new ArrayList<>(sharedPreferences.getAll().size());
//sharedPreferences.getAll()返回Map<String, ?>
for (Map.Entry<String, ?> entry : sharedPreferences.getAll().entrySet()) {
//遍历所有sharedPreferences中的键值对
//存储的cookie的字符串形式的值
String serializedCookie = (String) entry.getValue();
//将字符串使用序列化SerializableCookie解析为cookie对象
Cookie cookie = new SerializableCookie().decode(serializedCookie);
if (cookie != null) {
cookies.add(cookie);
}
}
return cookies;
}
//保存cookie列表到sharedPreferences中
@Override
public void saveAll(Collection<Cookie> cookies) {
SharedPreferences.Editor editor = sharedPreferences.edit();
for (Cookie cookie : cookies) {
//键格式:[http/https] + "://" + domain + path + "|" + cookename
//值:将对象序列化为字节流,在将字节流转为16进制的字符串
editor.putString(createCookieKey(cookie), new SerializableCookie().encode(cookie));
}
editor.commit();
}
//从sharedPreferences中删除指定的cookie列表
@Override
public void removeAll(Collection<Cookie> cookies) {
SharedPreferences.Editor editor = sharedPreferences.edit();
for (Cookie cookie : cookies) {
//遍历cookie列表
//删除指定键的cookie
editor.remove(createCookieKey(cookie));
}
editor.commit();
}
//生成cookie保存在sharedPreferences的键,格式为:[http/https] + "://" + domain + path + "|" + cookename
private static String createCookieKey(Cookie cookie) {
return (cookie.secure() ? "https" : "http") + "://" + cookie.domain() + cookie.path() + "|" + cookie.name();
}
//清空保存在sharedPreferences中的所有cookie
@Override
public void clear() {
sharedPreferences.edit().clear().commit();
}
}
PersistentCookieJar 采用SharedPreferences的方式,将cookie保存在sharedPreferences文件中,而保存时是将cookie序列化为字节流,再将字节流转为16进制的字符串;读取时再进行逆序处理解析得到Cookie对象(将16进制转为字节流,在将字节流反序列化为Cookie对象)。
我们看看序列化SerializableCookie是实现源码:
public class SerializableCookie implements Serializable {
private static final String TAG = SerializableCookie.class.getSimpleName();
private static final long serialVersionUID = -8594045714036645534L;
private transient Cookie cookie;
//将cookie序列化为字节流,再将字节流转为16进制的字符串
public String encode(Cookie cookie) {
this.cookie = cookie;
//创建字节输出流
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = null;
try {
//使用字节输出流构建对象输出流
objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
//将SerializableCookie写入对象输出流
objectOutputStream.writeObject(this);
} catch (IOException e) {
Log.d(TAG, "IOException in encodeCookie", e);
return null;
} finally {
if (objectOutputStream != null) {
try {
// Closing a ByteArrayOutputStream has no effect, it can be used later (and is used in the return statement)
objectOutputStream.close();
} catch (IOException e) {
Log.d(TAG, "Stream not closed in encodeCookie", e);
}
}
}
//提交对象输出流的字节流,再将字节流转为16进制的字符串
return byteArrayToHexString(byteArrayOutputStream.toByteArray());
}
//将字节流转为16进制的字符串
/**
* Using some super basic byte array <-> hex conversions so we don't
* have to rely on any large Base64 libraries. Can be overridden if you
* like!
*
* @param bytes byte array to be converted
* @return string containing hex values
*/
private static String byteArrayToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte element : bytes) {
//遍历字节流
//将字节与0xff进行与操作,将高位置为0,保证正数
int v = element & 0xff;
if (v < 16) {
//如果字节对应的int值小于16,在前面加0
sb.append('0');
}
//字节对应的int值转为16进制
sb.append(Integer.toHexString(v));
}
return sb.toString();
}
//将16进制转为字节流,再将字节流反序列化转为Cookie对象
public Cookie decode(String encodedCookie) {
//将16进制转为字节流
byte[] bytes = hexStringToByteArray(encodedCookie);
//创建字节输入流
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(
bytes);
Cookie cookie = null;
ObjectInputStream objectInputStream = null;
try {
//使用节输入流创建对象输入流
objectInputStream = new ObjectInputStream(byteArrayInputStream);
//使用对象输入流反序列化得到Cookie对象
cookie = ((SerializableCookie) objectInputStream.readObject()).cookie;
} catch (IOException e) {
Log.d(TAG, "IOException in decodeCookie", e);
} catch (ClassNotFoundException e) {
Log.d(TAG, "ClassNotFoundException in decodeCookie", e);
} finally {
if (objectInputStream != null) {
try {
objectInputStream.close();
} catch (IOException e) {
Log.d(TAG, "Stream not closed in decodeCookie", e);
}
}
}
return cookie;
}
//将16进制字符串转为字节流
/**
* Converts hex values from strings to byte array
*
* @param hexString string of hex-encoded values
* @return decoded byte array
*/
private static byte[] hexStringToByteArray(String hexString) {
//字符串长度
int len = hexString.length();
//字节流长度
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
//遍历字符串每两个字符
//将2个16进制字符转为字节
data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character
.digit(hexString.charAt(i + 1), 16));
}
return data;
}
private static long NON_VALID_EXPIRES_AT = -1L;
//序列化对象时,回调此方法将对象实例的cookie属性写入对象输出流中
private void writeObject(ObjectOutputStream out) throws IOException {
//将Cookie的属性写入对象输出流汇总
out.writeObject(cookie.name());
out.writeObject(cookie.value());
out.writeLong(cookie.persistent() ? cookie.expiresAt() : NON_VALID_EXPIRES_AT);
out.writeObject(cookie.domain());
out.writeObject(cookie.path());
out.writeBoolean(cookie.secure());
out.writeBoolean(cookie.httpOnly());
out.writeBoolean(cookie.hostOnly());
}
//反序列化对象时,回调此方法,从对象输入流读取cookie相关属性
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
//构建Cookie Builder对象
Cookie.Builder builder = new Cookie.Builder();
//按序列化时的顺序,从对象输入流中读取Cookie的属性
builder.name((String) in.readObject());
builder.value((String) in.readObject());
long expiresAt = in.readLong();
if (expiresAt != NON_VALID_EXPIRES_AT) {
builder.expiresAt(expiresAt);
}
final String domain = (String) in.readObject();
builder.domain(domain);
builder.path((String) in.readObject());
if (in.readBoolean())
builder.secure();
if (in.readBoolean())
builder.httpOnly();
if (in.readBoolean())
builder.hostOnlyDomain(domain);
cookie = builder.build();
}
}
SerializableCookie是Cookie序列化和反序列化的实现类,序列化是将Cookie的属性写入对象输入流中,以此得到Cookie对象的字节流,再将字节流转为16进制的字符串,作为保存在SharedPreferences中的值;反序列化是将16进制字符串先转为字节流,在将字节流反序列化(从字节流中按序列化时的顺序读取Cookie的属性)为Cookie对象。
总结
okhttp中cookie的操作是通过桥链拦截器BridgeInterceptor中借助CookieJar实现的,在请求前从CookieJar中读取url对应的cookie赋值到请求头部的Cookie字段;请求得到响应后,先从响应头中将set-cookie字符串列表转换为cookie列表,在使用CookieJar进行持久化存储。而okhttp默认情况下CookieJar是不做任何处理的,如果真需要实现Cookie的存取操作,可以自己实现CookieJar自行实现存取逻辑,也可以使用三方可如PersistentCookieJar实现存储。(cookie的存储最终是用什么方式存储可自行考量:直接文件、SharedPreferences、sqlite等)