前言:现在APP内网络框架使用Retrofit应该是很普遍的现象了,但是有时候由于业务的需求,需要维持登录状态,或者服务端需要往客户端写一些数据,然后下一次请求就把这些数据往服务端写回去。完全模拟一个PC端的浏览器机制,所以我这里使用了鸿洋大神的Retrofit对Cookie的支持。
现象
正常使用,发现没什么问题,JSESSIONID也可以正常使用,会话可以保持。昨天后台跟我说,他往客户端写了其他的COOKIE,为什么安卓端没有回传回来??
排查
既然不能传回去,有两个地方有嫌疑
- 第一个 Response往本地写没有写进去
- 第二个 Request往服务器传没有传过去
我们看okhttp3.CookieJar.java的源码:
public interface CookieJar {
/** A cookie jar that never accepts any cookies. */
CookieJar NO_COOKIES = new CookieJar() {
@Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
}
@Override public List<Cookie> loadForRequest(HttpUrl url) {
return Collections.emptyList();
}
};
/**
* Saves {@code cookies} from an HTTP response to this store according to this jar's policy.
*
* <p>Note that this method may be called a second time for a single HTTP response if the response
* includes a trailer. For this obscure HTTP feature, {@code cookies} contains only the trailer's
* cookies.
*/
void saveFromResponse(HttpUrl url, List<Cookie> cookies);
/**
* Load cookies from the jar for an HTTP request to {@code url}. This method returns a possibly
* empty list of cookies for the network request.
*
* <p>Simple implementations will return the accepted cookies that have not yet expired and that
* {@linkplain Cookie#matches match} {@code url}.
*/
List<Cookie> loadForRequest(HttpUrl url);
}
这里主要看两个方法:
-
void saveFromResponse(HttpUrl url, List<Cookie> cookies);
从Response往本地写 -
List<Cookie> loadForRequest(HttpUrl url);
从本地往Request请求里加进去
而对于本地化,则使用了一个PersistentCookieStore.java来实现
这里我们先看在 saveFromResponse(HttpUrl url, List<Cookie> cookies) 中,鸿洋大神写了这么一段:
override fun saveFromResponse(httpUrl: HttpUrl, cookies: List<Cookie>?)
{
if ( cookies != null && cookies.isNotEmpty())
{
for (item in cookies)
{
cookieStore.add(httpUrl, item)
}
}
}
这里就是简单的判断,以及将服务端返回来的cookie列表,一个一个往本地添加进去,那么我们跟踪代码:
cookieStore.add(httpUrl, item)
这一行点源码点进去:
PersistentCookieStore.java
public void add(HttpUrl url, Cookie cookie)
{
String name = getCookieToken(cookie);
//将cookies缓存到内存中 如果缓存过期 就重置此cookie
if (!cookie.persistent())
{
if (!cookies.containsKey(url.host()))
{
cookies.put(url.host(), new ConcurrentHashMap<String, Cookie>());
}
cookies.get(url.host()).put(name, cookie);
} else
{
if (cookies.containsKey(url.host()))
{
cookies.get(url.host()).remove(name);
}
}
//讲cookies持久化到本地
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
if (url.host() != null && cookies.get(url.host()) != null)
{
prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));
}
prefsWriter.putString(name, encodeCookie(new OkHttpCookies(cookie)));
prefsWriter.apply();
}
这里的逻辑是,先获取到他的token,token是怎么组成的呢?就是 域名 @ Cookie的Key
比如:127.0.0.1@key1 , 127.0.0.1@key2 , 127.0.0.1@key3
然后判断Cookie是否过期:
- 如果过期就New一个空的map添加进去。
- 如果没有过期,则判断在Cookie里面是否存有相同的Key的Cookie,如果有,则remove掉同样的name的旧cookie数据。
其实这里就有问题:当没有过期的时候,应该先remove掉旧的数据,再把新的数据添加进去,说白了就是应该覆盖旧数据,再把新数据持久化到本地。
解决问题
OK,问题已经找到了,所以我们这里把他代码修改一下:
public void add(HttpUrl url, Cookie cookie)
{
String name = getCookieToken(cookie);
//将cookies缓存到内存中 如果缓存过期 就重置此cookie
if (!cookie.persistent())
{
if (!cookies.containsKey(url.host()))
{
cookies.put(url.host(), new ConcurrentHashMap<String, Cookie>());
}
cookies.get(url.host()).put(name, cookie);
} else
{
// 这里是修改后的代码
Map<String, Cookie> cookieMap = cookies.get(url.host());
if (cookieMap != null)
{
cookieMap.put(name, cookie);
}
}
//讲cookies持久化到本地
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
if (url.host() != null && cookies.get(url.host()) != null)
{
prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));
}
prefsWriter.putString(name, encodeCookie(new OkHttpCookies(cookie)));
prefsWriter.apply();
}
这里我做了优化处理,先获取到对应host的cookieMap,如果不为空,则直接put进去。
为什么要这么做呢?我们这里先了解一下 cookies.containsKey(url.host()) 这行代码的意思是什么??意思是,在map中是否存在这个key,如果key存在,则返回true。但是如果刚好map中保存的是一个 null 呢??map中,key-value的形式,value是允许为null的。
所以我这里先获取到对应的value,判断不为空,再进行对应的处理,从逻辑上来讲,是比较安全的一个做法。然后就直接put进去,覆盖如果key一样,就覆盖旧数据了。再将cookies持久化的本地,这些就没什么可说的。
结尾
这里贴一下,我修改后的GitHub地址,以及Gradle直接使用地址:
GitHub : https://github.com/xiaolei123/OkHttpHelper
Gradle : implementation 'com.xiaolei:OkHttpUtil:1.0.6'
使用:
com.xiaolei.okhttputil.Cookie.CookieJar cookiejar = new CookieJar(context,null);
new OkHttpClient.Builder()
.cookieJar(cookiejar)
.build();
The End
网友评论