阅读 1936

OkHttp3入门介绍之Cookie持久化

版权所有,转载请注明出处:linzhiyong https://juejin.im/post/6844903646711267336 https://www.jianshu.com/p/23b35d403148

相关文章
1、OkHttp3入门介绍
2、OkHttp3入门介绍之Cookie持久化

前面文章介绍了OkHttp3的基本用法,GET/PST请求、上传下载文件等等,本章节主要介绍基于内存和本地缓存的Cookie管理。 官网:http://square.github.io/okhttp/
Github:https://github.com/square/okhttp
OkHttp3Demo传送门:https://github.com/linzhiyong/OkHttp3Demo
服务端Demo传送门:https://github.com/linzhiyong/SpringMVCDemo

目录

本章主要从以下几个方面介绍:
1、OkHttp3 Cookie内置管理机制介绍
2、基于本地存储的Cookie管理
3、基于内存存储的Cookie管理
4、总结

1、OkHttp3 Cookie内置管理机制介绍

OkHttp提供了用于管理Cookie的接口CookieJar,看一下接口的内部结构:

public interface CookieJar {
  /** 内部默认实现,不做任何操作. */
  CookieJar NO_COOKIES = new CookieJar() {
    @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
    }

    @Override public List<Cookie> loadForRequest(HttpUrl url) {
      return Collections.emptyList();
    }
  };

  /** 调用网络请求,获取到cookie相关信息后,okhttp会回调该方法,此处可以缓存或者持久化cookie */
  void saveFromResponse(HttpUrl url, List<Cookie> cookies);

  /** 请求时,okhttp会通过该方法,获取对应的cookie */
  List<Cookie> loadForRequest(HttpUrl url);
}
复制代码

从上面可以看出,CookieJar接口提供了saveFromResponseloadForRequest 两个方法,还有一个内部类默认实现NO_COOKIES。 1)saveFromResponse方法:当网络请求返回结果后,内部会解析Header并获取cookie相关信息,同时回调该方法,此处可以缓存或者持久化cookie,下面看一下调用源码:

public final class BridgeInterceptor implements Interceptor {
// 此处省略
  @Override public Response intercept(Chain chain) throws IOException {
    // 此处省略...
   
    // 通过cookieJar接口的loadForRequest方法获取url对应的cookie
    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
      // 如果获取到的cookie不为空,则设置到请求头中
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }
   
 // 此处省略
  }
}
复制代码

2)loadForRequest 方法:当网络请求时,okhttp会通过该方法,获取对应cookie,下面看一下调用源码:

public final class BridgeInterceptor implements Interceptor {
// 此处省略
  @Override public Response intercept(Chain chain) throws IOException {
    // 此处省略...

    // 此处获取请求的响应对象
    Response networkResponse = chain.proceed(requestBuilder.build());
    // 解析响应头里的信息
    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
  }
}

public final class HttpHeaders {
  
  public static void receiveHeaders(CookieJar cookieJar, HttpUrl url, Headers headers) {
    if (cookieJar == CookieJar.NO_COOKIES) return;
    // 解析响应头里的信息
    List<Cookie> cookies = Cookie.parseAll(url, headers);
    if (cookies.isEmpty()) return;
    // 调用cookieJar接口的saveFromResponse方法,下发cookie
    cookieJar.saveFromResponse(url, cookies);
  }

}
复制代码

3)如果开发者在初始化OkHtpClient时没有自定义CookieJar,默认不会进行cookie操作,看一下OkHttpClient的构造器实现;

public static final class Builder {
  CookieJar cookieJar;
  public Builder() {
    // 默认使用自带cookie管理器,没有做任何cookie处理
    cookieJar = CookieJar.NO_COOKIES;
  }
}
复制代码

2、基于本地存储的Cookie管理

这里我仿照 android-async-http 的Cookie管理机制PersistentCookieStore 进行改造;

实现逻辑 1、定义用于管理Cookie的接口CookieStore; 2、定义CookieJarImpl类实现CookieJar接口,然后用CookieStore去接管saveFromResponseloadForRequest 这两个方法; 3、定义PersistentCookieStore类实现CookieStore接口,用于管理Cookie; 4、将PersistentCookieStore对象设置到OkHttpClient中;

具体实现 1、定义CookieStore接口:

/**
 * Cookie缓存接口
 *
 * @author linzhiyong
 * @email wflinzhiyong@163.com
 * @blog https://blog.csdn.net/u012527802
 * @desc
 */
public interface CookieStore {

    /**  添加cookie */
    void add(HttpUrl httpUrl, Cookie cookie);

    /** 添加指定httpurl cookie集合 */
    void add(HttpUrl httpUrl, List<Cookie> cookies);

    /** 根据HttpUrl从缓存中读取cookie集合 */
    List<Cookie> get(HttpUrl httpUrl);

    /** 获取全部缓存cookie */
    List<Cookie> getCookies();

    /**  移除指定httpurl cookie集合 */
    boolean remove(HttpUrl httpUrl, Cookie cookie);

    /** 移除所有cookie */
    boolean removeAll();
}
复制代码

2、定义CookieJarImpl类实现CookieJar接口,然后用CookieStore去接管saveFromResponseloadForRequest 这两个方法:

/**
 * CookieJarImpl
 *
 * @author linzhiyong
 * @email wflinzhiyong@163.com
 * @blog https://blog.csdn.net/u012527802
 * @time 2018/7/20
 * @desc
 */
public class CookieJarImpl implements CookieJar {

    private CookieStore cookieStore;

    public CookieJarImpl(CookieStore cookieStore) {
        if(cookieStore == null) {
            throw new IllegalArgumentException("cookieStore can not be null.");
        }
        this.cookieStore = cookieStore;
    }

    @Override
    public synchronized void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
        this.cookieStore.add(url, cookies);
    }

    @Override
    public synchronized List<Cookie> loadForRequest(HttpUrl url) {
        return this.cookieStore.get(url);
    }

    public CookieStore getCookieStore() {
        return this.cookieStore;
    }
}
复制代码

3、定义PersistentCookieStore类实现CookieStore接口,用于管理Cookie; (:这里仿照android-async-http库里的 PersistentCookieStore 实现)

/**
 * Cookie缓存持久化实现类
 *
 * @author linzhiyong
 * @email wflinzhiyong@163.com
 * @blog https://blog.csdn.net/u012527802
 * @time 2018/7/20
 * @desc
 */
public class PersistentCookieStore implements CookieStore {

    private static final String LOG_TAG = "PersistentCookieStore";
    private static final String COOKIE_PREFS = "CookiePrefsFile";
    private static final String HOST_NAME_PREFIX = "host_";
    private static final String COOKIE_NAME_PREFIX = "cookie_";
    private final HashMap<String, ConcurrentHashMap<String, Cookie>> cookies;
    private final SharedPreferences cookiePrefs;
    private boolean omitNonPersistentCookies = false;

    /** Construct a persistent cookie store.  */
    public PersistentCookieStore(Context context) {
        this.cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);
        this.cookies = new HashMap<String, ConcurrentHashMap<String, Cookie>>();

        Map tempCookieMap = new HashMap<Object, Object>(cookiePrefs.getAll());
        for (Object key : tempCookieMap.keySet()) {
            if (!(key instanceof String) || !((String) key).contains(HOST_NAME_PREFIX)) {
                continue;
            }

            String cookieNames = (String) tempCookieMap.get(key);
            if (TextUtils.isEmpty(cookieNames)) {
                continue;
            }

            if (!this.cookies.containsKey(key)) {
                this.cookies.put((String) key, new ConcurrentHashMap<String, Cookie>());
            }

            String[] cookieNameArr = cookieNames.split(",");
            for (String name : cookieNameArr) {
                String encodedCookie = this.cookiePrefs.getString("cookie_" + name, null);
                if (encodedCookie == null) {
                    continue;
                }

                Cookie decodedCookie = this.decodeCookie(encodedCookie);
                if (decodedCookie != null) {
                   this.cookies.get(key).put(name, decodedCookie);
                }
            }
        }
        tempCookieMap.clear();
 
        clearExpired();
    }

    /** 移除失效cookie */
    private void clearExpired() {
        SharedPreferences.Editor prefsWriter = cookiePrefs.edit();

        for (String key : this.cookies.keySet()) {
            boolean changeFlag = false;

            for (ConcurrentHashMap.Entry<String, Cookie> entry : cookies.get(key).entrySet()) {
                String name = entry.getKey();
                Cookie cookie = entry.getValue();
                if (isCookieExpired(cookie)) {
                    // Clear cookies from local store
                    cookies.get(key).remove(name);

                    // Clear cookies from persistent store
                    prefsWriter.remove(COOKIE_NAME_PREFIX + name);

                    changeFlag = true;
                }
            }

            // Update names in persistent store
            if (changeFlag) {
                prefsWriter.putString(key, TextUtils.join(",", cookies.keySet()));
            }
        }

        prefsWriter.apply();
    }

    @Override
    public void add(HttpUrl httpUrl, Cookie cookie) {
        if (omitNonPersistentCookies && !cookie.persistent()) {
            return;
        }

        String name = this.cookieName(cookie);
        String hostKey = this.hostName(httpUrl);

        // Save cookie into local store, or remove if expired
        if(!this.cookies.containsKey(hostKey)) {
            this.cookies.put(hostKey, new ConcurrentHashMap<String, Cookie>());
        }
        cookies.get(hostKey).put(name, cookie);

        // Save cookie into persistent store
        SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
        // 保存httpUrl对应的所有cookie的name
        prefsWriter.putString(hostKey, TextUtils.join(",", cookies.get(hostKey).keySet()));
        // 保存cookie
        prefsWriter.putString(COOKIE_NAME_PREFIX + name, encodeCookie(new SerializableCookie(cookie)));
        prefsWriter.apply();
    }

    @Override
    public void add(HttpUrl httpUrl, List<Cookie> cookies) {
        for (Cookie cookie : cookies) {
            if (isCookieExpired(cookie)) {
                continue;
            }
            this.add(httpUrl, cookie);
        }
    }

    @Override
    public List<Cookie> get(HttpUrl httpUrl) {
        return this.get(this.hostName(httpUrl));
    }

    @Override
    public List<Cookie> getCookies() {
        ArrayList<Cookie> result = new ArrayList<Cookie>();
        for (String hostKey : this.cookies.keySet()) {
            result.addAll(this.get(hostKey));
        }
        return result;
    }

    /** 获取cookie集合 */
    private List<Cookie> get(String hostKey) {
        ArrayList<Cookie> result = new ArrayList<Cookie>();

        if (this.cookies.containsKey(hostKey)) {
            Collection<Cookie> cookies = this.cookies.get(hostKey).values();
            for (Cookie cookie : cookies) {
                if (isCookieExpired(cookie)) {
                    this.remove(hostKey, cookie);
                }
                else {
                    result.add(cookie);
                }
            }
        }
        return result;
    }

    @Override
    public boolean remove(HttpUrl httpUrl, Cookie cookie) {
        return this.remove(this.hostName(httpUrl), cookie);
    }

    /** 从缓存中移除cookie */
    private boolean remove(String hostKey, Cookie cookie) {
        String name = this.cookieName(cookie);
        if (this.cookies.containsKey(hostKey) && this.cookies.get(hostKey).containsKey(name)) {
            // 从内存中移除httpUrl对应的cookie
            this.cookies.get(hostKey).remove(name);

            SharedPreferences.Editor prefsWriter = cookiePrefs.edit();

            // 从本地缓存中移出对应cookie
            prefsWriter.remove(COOKIE_NAME_PREFIX + name);

            // 保存httpUrl对应的所有cookie的name
            prefsWriter.putString(hostKey, TextUtils.join(",", this.cookies.get(hostKey).keySet()));

            prefsWriter.apply();
            return true;
        }
        return false;
    }

    @Override
    public boolean removeAll() {
        SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
        prefsWriter.clear();
        prefsWriter.apply();
        this.cookies.clear();
        return true;
    }

    public void setOmitNonPersistentCookies(boolean omitNonPersistentCookies) {
        this.omitNonPersistentCookies = omitNonPersistentCookies;
    }

    /** 判断cookie是否失效  */
    private boolean isCookieExpired(Cookie cookie) {
        return cookie.expiresAt() < System.currentTimeMillis();
    }

    private String hostName(HttpUrl httpUrl) {
        return httpUrl.host().startsWith(HOST_NAME_PREFIX) ? httpUrl.host() : HOST_NAME_PREFIX + httpUrl.host();
    }

    private String cookieName(Cookie cookie) {
        return cookie == null ? null : cookie.name() + cookie.domain();
    }

    protected String encodeCookie(SerializableCookie 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());
    }

    protected Cookie decodeCookie(String cookieString) {
        byte[] bytes = hexStringToByteArray(cookieString);
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        Cookie cookie = null;
        try {
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            cookie = ((SerializableCookie) objectInputStream.readObject()).getCookie();
        } catch (IOException e) {
            Log.d(LOG_TAG, "IOException in decodeCookie", e);
        } catch (ClassNotFoundException e) {
            Log.d(LOG_TAG, "ClassNotFoundException in decodeCookie", e);
        }
        return cookie;
    }

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

    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;
    }
}
复制代码

这里面用到了SerializableCookie,主要用于序列表cookie对象到对象流中:

/**
 * 仿照android-async-http的SerializableCookie实现,用处是cookie对象与对象流的互转,保存和读取cookie
 *
 * @author linzhiyong
 * @email wflinzhiyong@163.com
 * @blog https://blog.csdn.net/u012527802
 * @time 2018/7/20
 * @desc
 */
public class SerializableCookie implements Serializable {
    private static final long serialVersionUID = 6374381828722046732L;

    private transient final Cookie cookie;
    private transient Cookie clientCookie;

    public SerializableCookie(Cookie cookie) {
        this.cookie = cookie;
    }

    public Cookie getCookie() {
        Cookie bestCookie = cookie;
        if (this.clientCookie != null) {
            bestCookie = this.clientCookie;
        }
        return bestCookie;
    }

    /** 将cookie写到对象流中 */
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.writeObject(this.cookie.name());
        out.writeObject(this.cookie.value());
        out.writeLong(this.cookie.expiresAt());
        out.writeObject(this.cookie.domain());
        out.writeObject(this.cookie.path());
        out.writeBoolean(this.cookie.secure());
        out.writeBoolean(this.cookie.httpOnly());
        out.writeBoolean(this.cookie.hostOnly());
        out.writeBoolean(this.cookie.persistent());
    }

    /** 从对象流中构建cookie对象 */
    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()
                .name(name)
                .value(value)
                .expiresAt(expiresAt)
                .path(path);
        builder = hostOnly ? builder.hostOnlyDomain(domain) : builder.domain(domain);
        builder = secure ? builder.secure() : builder;
        builder = httpOnly ? builder.httpOnly() : builder;
        this.clientCookie = builder.build();
    }
}
复制代码

4、将PersistentCookieStore对象设置到OkHttpClient中;

OkHttpClient.Builder builder = new OkHttpClient.Builder()
    .cookieJar(new CookieJarImpl(new PersistentCookieStore(context)));
复制代码

3、基于内存存储的Cookie管理

实现逻辑跟PersistentCookieStore类似,只是对于Cookie的存储放在了Map中。

/**
 * Cookie内存缓存实现
 *
 * @author linzhiyong
 * @email wflinzhiyong@163.com
 * @blog https://blog.csdn.net/u012527802 
 * @time 2018/7/20
 * @desc
 */
public class MemoryCookieStore implements CookieStore {

    private static final String HOST_NAME_PREFIX = "host_";
    private static final String COOKIE_NAME_PREFIX = "cookie_";

    private final HashMap<String, ConcurrentHashMap<String, Cookie>> cookies;

    public MemoryCookieStore() {
        this.cookies = new HashMap<String, ConcurrentHashMap<String, Cookie>>();
    }

    @Override
    public void add(HttpUrl httpUrl, Cookie cookie) {
        if (!cookie.persistent()) {
            return;
        }

        String name = this.cookieName(cookie);
        String hostKey = this.hostName(httpUrl);
 
        if(!this.cookies.containsKey(hostKey)) {
            this.cookies.put(hostKey, new ConcurrentHashMap<String, Cookie>());
        }
        cookies.get(hostKey).put(name, cookie);
    }

    @Override
    public void add(HttpUrl httpUrl, List<Cookie> cookies) {
        for (Cookie cookie : cookies) {
            if (isCookieExpired(cookie)) {
                continue;
            }
            this.add(httpUrl, cookie);
        }
    }

    @Override
    public List<Cookie> get(HttpUrl httpUrl) {
        return this.get(this.hostName(httpUrl));
    }

    @Override
    public List<Cookie> getCookies() {
        ArrayList<Cookie> result = new ArrayList<Cookie>();

        for (String hostKey : this.cookies.keySet()) {
            result.addAll(this.get(hostKey));
        }

        return result;
    }

    /** 获取cookie集合 */
    private List<Cookie> get(String hostKey) {
        ArrayList<Cookie> result = new ArrayList<Cookie>();

        if (this.cookies.containsKey(hostKey)) {
            Collection<Cookie> cookies = this.cookies.get(hostKey).values();
            for (Cookie cookie : cookies) {
                if (isCookieExpired(cookie)) {
                    this.remove(hostKey, cookie);
                }
                else {
                    result.add(cookie);
                }
            }
        }
        return result;
    }

    @Override
    public boolean remove(HttpUrl httpUrl, Cookie cookie) {
        return this.remove(this.hostName(httpUrl), cookie);
    }

    /** 从缓存中移除cookie */
    private boolean remove(String hostKey, Cookie cookie) {
        String name = this.cookieName(cookie);
        if (this.cookies.containsKey(hostKey) && this.cookies.get(hostKey).containsKey(name)) {
            // 从内存中移除httpUrl对应的cookie
            this.cookies.get(hostKey).remove(name);
            return true;
        }
        return false;
    }

    @Override
    public boolean removeAll() {
        this.cookies.clear();
        return true;
    }

    /** 判断cookie是否失效 */
    private boolean isCookieExpired(Cookie cookie) {
        return cookie.expiresAt() < System.currentTimeMillis();
    }

    private String hostName(HttpUrl httpUrl) {
        return httpUrl.host().startsWith(HOST_NAME_PREFIX) ? httpUrl.host() : HOST_NAME_PREFIX + httpUrl.host();
    }

    private String cookieName(Cookie cookie) {
        return cookie == null ? null : cookie.name() + cookie.domain();
    }
}
复制代码

4、总结

今天的主要就是介绍了Cookie的管理,就是从CookieJar接口的两个方法入手,然后做了进一步的封装处理,PersistentCookieStoreMemoryCookieStore这两个类的逻辑实现基本一致,喜欢动手的小伙伴完全可以进一步抽象一下。

文章分类
Android