踩踩鸿洋大神的坑,优化修复他Cookie支持带来的BUG

2018-06-28 11:35:35 浏览数 (1)

前言:现在APP内网络框架使用Retrofit应该是很普遍的现象了,但是有时候由于业务的需求,需要维持登录状态,或者服务端需要往客户端写一些数据,然后下一次请求就把这些数据往服务端写回去。完全模拟一个PC端的浏览器机制,所以我这里使用了鸿洋大神的Retrofit对Cookie的支持。

现象

正常使用,发现没什么问题,JSESSIONID也可以正常使用,会话可以保持。昨天后台跟我说,他往客户端写了其他的COOKIE,为什么安卓端没有回传回来??

排查

既然不能传回去,有两个地方有嫌疑

  • 第一个 Response往本地写没有写进去
  • 第二个 Request往服务器传没有传过去

我们看okhttp3.CookieJar.java的源码:

代码语言:javascript复制
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) 中,鸿洋大神写了这么一段:

代码语言:javascript复制
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

代码语言:javascript复制
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,问题已经找到了,所以我们这里把他代码修改一下:

代码语言:javascript复制
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' 使用:

代码语言:javascript复制
com.xiaolei.okhttputil.Cookie.CookieJar cookiejar = new CookieJar(context,null);
new OkHttpClient.Builder()
        .cookieJar(cookiejar)
        .build();

The End

0 人点赞