zoukankan      html  css  js  c++  java
  • OkHttp3实现Cookies管理及持久化

    转发自:http://www.codeceo.com/article/okhttp3-cookies-manage.html

    OKHttp3正式版刚发布了没几天,正好重构之前的代码,于是第一时间入坑了。对okHttp3的一些改变,会陆续写下来,这是第一篇Cookies管理及持久化。

    Cookies管理

    OkHttp的源码过于复杂,感兴趣的同学可以自行阅读,这里只针对 HttpEngineer 类进行分析,从字面意思即可看出这个类负责http请求的request、response等等操作的处理,而cookies管理也是随着http请求的request、response来处理。

    3.0之前

    先看networkRequest方法,在里面通过client.getCookieHandler()函数获得了CookieHandler对象,通过该对象拿到cookie并设置到请求头里,请求结束后取得响应后通过networkResponse.headers()函数将请求头获得传入receiveHeaders函数,并将取得的cookie存入getCookieHandler得到的一个CookieHandler对象中去

    private Request networkRequest(Request request) throws IOException {
      Request.Builder result = request.newBuilder();
    
      //例行省略....
    
      CookieHandler cookieHandler = client.getCookieHandler();
      if (cookieHandler != null) {
        // Capture the request headers added so far so that they can be offered to the CookieHandler.
        // This is mostly to stay close to the RI; it is unlikely any of the headers above would
        // affect cookie choice besides "Host".
        Map<String, List<String>> headers = OkHeaders.toMultimap(result.build().headers(), null);
    
        Map<String, List<String>> cookies = cookieHandler.get(request.uri(), headers);
    
        // Add any new cookies to the request.
        OkHeaders.addCookies(result, cookies);
      }
    
      //例行省略....
    
      return result.build();
    }
    public void readResponse() throws IOException {
      //例行省略....
    
      receiveHeaders(networkResponse.headers());
    
      //例行省略....
    }
    public void receiveHeaders(Headers headers) throws IOException {
      CookieHandler cookieHandler = client.getCookieHandler();
      if (cookieHandler != null) {
        cookieHandler.put(userRequest.uri(), OkHeaders.toMultimap(headers, null));
      }
    }

    CookieHandler对象是OkHttpClient类中的一个属性,传入了这个对象,那么OkHttp就会对cookie进行自动管理

    private CookieHandler cookieHandler;
    public OkHttpClient setCookieHandler(CookieHandler cookieHandler) {
      this.cookieHandler = cookieHandler;
      return this;
    }
    
    public CookieHandler getCookieHandler() {
      return cookieHandler;
    }
    OkHttpClient client = new OkHttpClient();
    client.setCookieHandler(CookieHandler cookieHanlder);

    3.0之后

    而在OkHttp3中,对cookie而言,新增了两个类 Cookiejar 、 Cookie 两个类,在了解这两个类之前,先去看一下 HttpEngine 关于cookie管理的变化

    private Request networkRequest(Request request) throws IOException {
        Request.Builder result = request.newBuilder();
    
        //例行省略....
    
        List<Cookie> cookies = client.cookieJar().loadForRequest(request.url());
        if (!cookies.isEmpty()) {
          result.header("Cookie", cookieHeader(cookies));
        }
    
        //例行省略....
    
        return result.build();
      }
    private String cookieHeader(List<Cookie> cookies) {
        StringBuilder cookieHeader = new StringBuilder();
        for (int i = 0, size = cookies.size(); i < size; i++) {
          if (i > 0) {
            cookieHeader.append("; ");
          }
          Cookie cookie = cookies.get(i);
          cookieHeader.append(cookie.name()).append('=').append(cookie.value());
        }
        return cookieHeader.toString();
      }
    public void receiveHeaders(Headers headers) throws IOException {
        if (client.cookieJar() == CookieJar.NO_COOKIES) return;
    
        List<Cookie> cookies = Cookie.parseAll(userRequest.url(), headers);
        if (cookies.isEmpty()) return;
    
        client.cookieJar().saveFromResponse(userRequest.url(), cookies);
      }

    通过以上几个关键方法,可以很明显的感觉到作者的意图了,为了更加自由定制化的cookie管理。其中 loadForRequest() 、 saveFromResponse() 这两个方法最为关键,分别是在发送时向request header中加入cookie,在接收时,读取response header中的cookie。现在再去看 Cookiejar 这个类,就很好理解了

    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);
    }

    so!在OkHttpClient创建时,传入这个CookieJar的实现,就能完成对Cookie的自动管理了

    OkHttpClient client = new OkHttpClient.Builder()
        .cookieJar(new CookieJar() {
            private final HashMap<HttpUrl, List<Cookie>> cookieStore = new HashMap<>();
    
            @Override
            public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
                cookieStore.put(url, cookies);
            }
    
            @Override
            public List<Cookie> loadForRequest(HttpUrl url) {
                List<Cookie> cookies = cookieStore.get(url);
                return cookies != null ? cookies : new ArrayList<Cookie>();
            }
        })
        .build();

    Cookies持久化

    对Cookies持久化的方案,与之前版本并无很大区别,还是参考 android-async-http 这个库,主要参考其中两个类:

    • PersistentCookieStore
    • SerializableHttpCookie
      与之前版本的区别是要将对 java.net.HttpCookie 这个类的缓存处理换成对 okhttp3.Cookie 的处理,其他方面几乎一样。

    废话不多说了,直接上代码

    SerializableOkHttpCookies

    主要做两件事:

    • 将Cookie对象输出为ObjectStream
    • 将ObjectStream序列化成Cookie对象
    public class SerializableOkHttpCookies implements Serializable {
    
        private transient final Cookie cookies;
        private transient Cookie clientCookies;
    
        public SerializableOkHttpCookies(Cookie cookies) {
            this.cookies = cookies;
        }
    
        public Cookie getCookies() {
            Cookie bestCookies = cookies;
            if (clientCookies != null) {
                bestCookies = clientCookies;
            }
            return bestCookies;
        }
    
        private void writeObject(ObjectOutputStream out) throws IOException {
            out.writeObject(cookies.name());
            out.writeObject(cookies.value());
            out.writeLong(cookies.expiresAt());
            out.writeObject(cookies.domain());
            out.writeObject(cookies.path());
            out.writeBoolean(cookies.secure());
            out.writeBoolean(cookies.httpOnly());
            out.writeBoolean(cookies.hostOnly());
            out.writeBoolean(cookies.persistent());
        }
    
        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
            String name = (String) in.readObject();
            String value = (String) in.readObject();
            long expiresAt = in.readLong();
            String domain = (String) in.readObject();
            String path = (String) in.readObject();
            boolean secure = in.readBoolean();
            boolean httpOnly = in.readBoolean();
            boolean hostOnly = in.readBoolean();
            boolean persistent = in.readBoolean();
            Cookie.Builder builder = new Cookie.Builder();
            builder = builder.name(name);
            builder = builder.value(value);
            builder = builder.expiresAt(expiresAt);
            builder = hostOnly ? builder.hostOnlyDomain(domain) : builder.domain(domain);
            builder = builder.path(path);
            builder = secure ? builder.secure() : builder;
            builder = httpOnly ? builder.httpOnly() : builder;
            clientCookies =builder.build();
        }
    }

    PersistentCookieStore

    根据一定的规则去缓存或者获取Cookie:

    public class PersistentCookieStore {
        private static final String LOG_TAG = "PersistentCookieStore";
        private static final String COOKIE_PREFS = "Cookies_Prefs";
    
        private final Map<String, ConcurrentHashMap<String, Cookie>> cookies;
        private final SharedPreferences cookiePrefs;
    
        public PersistentCookieStore(Context context) {
            cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);
            cookies = new HashMap<>();
    
            //将持久化的cookies缓存到内存中 即map cookies
            Map<String, ?> prefsMap = cookiePrefs.getAll();
            for (Map.Entry<String, ?> entry : prefsMap.entrySet()) {
                String[] cookieNames = TextUtils.split((String) entry.getValue(), ",");
                for (String name : cookieNames) {
                    String encodedCookie = cookiePrefs.getString(name, null);
                    if (encodedCookie != null) {
                        Cookie decodedCookie = decodeCookie(encodedCookie);
                        if (decodedCookie != null) {
                            if (!cookies.containsKey(entry.getKey())) {
                                cookies.put(entry.getKey(), new ConcurrentHashMap<String, Cookie>());
                            }
                            cookies.get(entry.getKey()).put(name, decodedCookie);
                        }
                    }
                }
            }
        }
    
        protected String getCookieToken(Cookie cookie) {
            return cookie.name() + "@" + cookie.domain();
        }
    
        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();
            prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));
            prefsWriter.putString(name, encodeCookie(new SerializableOkHttpCookies(cookie)));
            prefsWriter.apply();
        }
    
        public List<Cookie> get(HttpUrl url) {
            ArrayList<Cookie> ret = new ArrayList<>();
            if (cookies.containsKey(url.host()))
                ret.addAll(cookies.get(url.host()).values());
            return ret;
        }
    
        public boolean removeAll() {
            SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
            prefsWriter.clear();
            prefsWriter.apply();
            cookies.clear();
            return true;
        }
    
        public boolean remove(HttpUrl url, Cookie cookie) {
            String name = getCookieToken(cookie);
    
            if (cookies.containsKey(url.host()) && cookies.get(url.host()).containsKey(name)) {
                cookies.get(url.host()).remove(name);
    
                SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
                if (cookiePrefs.contains(name)) {
                    prefsWriter.remove(name);
                }
                prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));
                prefsWriter.apply();
    
                return true;
            } else {
                return false;
            }
        }
    
        public List<Cookie> getCookies() {
            ArrayList<Cookie> ret = new ArrayList<>();
            for (String key : cookies.keySet())
                ret.addAll(cookies.get(key).values());
    
            return ret;
        }
    
        /**
         * cookies 序列化成 string
         *
         * @param cookie 要序列化的cookie
         * @return 序列化之后的string
         */
        protected String encodeCookie(SerializableOkHttpCookies cookie) {
            if (cookie == null)
                return null;
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            try {
                ObjectOutputStream outputStream = new ObjectOutputStream(os);
                outputStream.writeObject(cookie);
            } catch (IOException e) {
                Log.d(LOG_TAG, "IOException in encodeCookie", e);
                return null;
            }
    
            return byteArrayToHexString(os.toByteArray());
        }
    
        /**
         * 将字符串反序列化成cookies
         *
         * @param cookieString cookies string
         * @return cookie object
         */
        protected Cookie decodeCookie(String cookieString) {
            byte[] bytes = hexStringToByteArray(cookieString);
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
            Cookie cookie = null;
            try {
                ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
                cookie = ((SerializableOkHttpCookies) objectInputStream.readObject()).getCookies();
            } catch (IOException e) {
                Log.d(LOG_TAG, "IOException in decodeCookie", e);
            } catch (ClassNotFoundException e) {
                Log.d(LOG_TAG, "ClassNotFoundException in decodeCookie", e);
            }
    
            return cookie;
        }
    
        /**
         * 二进制数组转十六进制字符串
         *
         * @param bytes byte array to be converted
         * @return string containing hex values
         */
        protected String byteArrayToHexString(byte[] bytes) {
            StringBuilder sb = new StringBuilder(bytes.length * 2);
            for (byte element : bytes) {
                int v = element & 0xff;
                if (v < 16) {
                    sb.append('0');
                }
                sb.append(Integer.toHexString(v));
            }
            return sb.toString().toUpperCase(Locale.US);
        }
    
        /**
         * 十六进制字符串转二进制数组
         *
         * @param hexString string of hex-encoded values
         * @return decoded byte array
         */
        protected byte[] hexStringToByteArray(String hexString) {
            int len = hexString.length();
            byte[] data = new byte[len / 2];
            for (int i = 0; i < len; i += 2) {
                data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16));
            }
            return data;
        }
    }

    最终效果

    完成对Cookie持久化之后,就可以对Cookiejar进行进一步修改了,最终效果:

    /**
         * 自动管理Cookies
         */
        private class CookiesManager implements CookieJar {
            private final PersistentCookieStore cookieStore = new PersistentCookieStore(getApplicationContext());
    
            @Override
            public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
                if (cookies != null && cookies.size() > 0) {
                    for (Cookie item : cookies) {
                        cookieStore.add(url, item);
                    }
                }
            }
    
            @Override
            public List<Cookie> loadForRequest(HttpUrl url) {
                List<Cookie> cookies = cookieStore.get(url);
                return cookies;
            }
        }

    Tips

    在这样做之前,尝试了使用 Interceptor 和 NetWorkInterceptor 在Http请求request和response时,拦截响应链,加入对Cookie的管理。so!接下来可能会详细介绍下 Interceptor 这个非常酷的实现。

    我使用的是Okhttp3,用到了上面提到的SerializableOkHttpCookies、PersistentCookieStore以及CookiesManager 。

    下面是我的封装类中关于OkHttpClient的初始化:

    private OkHttp3Utils() {
            super();
            OkHttpClient.Builder clientBuilder = new OkHttpClient().newBuilder();
            clientBuilder.readTimeout(30, TimeUnit.SECONDS);
            clientBuilder.connectTimeout(15, TimeUnit.SECONDS);
            clientBuilder.writeTimeout(60, TimeUnit.SECONDS);
            clientBuilder.cookieJar(new CookiesManager());
            mOkHttpClient = clientBuilder.build();
        }
    View Code

    加入到项目中,成功运行!

  • 相关阅读:
    CSS自定义三角形
    完整例子-正则控制input的输入
    vux环境配置
    纯JS实现加载更多(VUE框架)
    随时监测屏幕大小,解决手机端小键盘遮挡输入框问题
    [转]Javascript中几种较为流行的继承方式
    使用字面量,比new更加有效
    2.ES6引进的新特性——类Class
    vue-router 基本使用
    插槽slot
  • 原文地址:https://www.cnblogs.com/shenchanghui/p/6409699.html
Copyright © 2011-2022 走看看