当前位置

网站首页> 程序设计 > 开源项目 > 程序开发 > 浏览文章

OkHttp3之Cookies管理及持久化 - Akioss

作者:小梦 来源: 网络 时间: 2024-08-24 阅读:

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而言,新增了两个类CookiejarCookie两个类,在了解这两个类之前,先去看一下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

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

热点阅读

网友最爱