美文网首页Android开发经验谈Android开发半栈工程师
踩踩鸿洋大神的坑,优化修复他Cookie支持带来的BUG

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

作者: xiaolei123 | 来源:发表于2018-02-07 10:46 被阅读352次

    前言:现在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

    相关文章

      网友评论

        本文标题:踩踩鸿洋大神的坑,优化修复他Cookie支持带来的BUG

        本文链接:https://www.haomeiwen.com/subject/oulozxtx.html