美文网首页奶牛刀
okhttp原理解析之cookie

okhttp原理解析之cookie

作者: jxiang112 | 来源:发表于2021-12-21 14:47 被阅读0次

建议先对HTTP有个大概的了解:HTTP概述

okhttp原理解析之整体流程
okhttp原理解析之cookie
okhttp 缓存解析
[okhttp Dns解析](# 待续)
[okhttp 连接解析](# 待续)
[okhttp http解析](# 待续)
[okhttp https解析](# 待续)
[okhttp io解析](# 待续)

桥链拦截器源码解析

okhttp原理解析之整体流程的解析中,cookie是责任链中第二个执行的链条,其是在BridgeInterceptor(桥链拦截器)中实现的,我们先看看BridgeInterceptor的源码:

public final class BridgeInterceptor implements Interceptor {
   //HTTP Cookie处理器:cookie存储等
    private final CookieJar cookieJar;

    public BridgeInterceptor(CookieJar cookieJar) {
        this.cookieJar = cookieJar;
    }
    
    //桥链拦截器的责任
    //chain:下一个责任
    public Response intercept(Chain chain) throws IOException {
        //请求内容对象
        Request userRequest = chain.request();
       //重构请求内容对象的buider
        Builder requestBuilder = userRequest.newBuilder();
       //请求内容体体
        RequestBody body = userRequest.body();
        if (body != null) {
            //如果请求内容体不为空
            //请求内容体的媒体类型,如:text/html、image/png等
            MediaType contentType = body.contentType();
            if (contentType != null) {
                //如果请求内容体的媒体类型不为空
                //设置HTTP头部媒体类型
                requestBuilder.header("Content-Type", contentType.toString());
            }
            ///请求内容体的内容长度,单位字节
            long contentLength = body.contentLength();
            if (contentLength != -1L) {
                //如果请求内容体的内容长度不为-1,表示是整体传输
                //设置HTTP头部的Content-Length(内容长度)
                requestBuilder.header("Content-Length", Long.toString(contentLength));
                 //删除HTTP头部的Transfer-Encoding(传输编码:逐跳传输内容,每一端可以使用不同的编码,由于此分支是整体传输,故不需要逐跳)
                requestBuilder.removeHeader("Transfer-Encoding");
            } else {
                //如果请求内容体的内容长度 == -1,表示分段传输
                //设置HTTP头部的Transfer-Encoding(传输编码:逐跳传输内容,每一端可以使用不同的编码,由于此分支是逐跳传输)
                requestBuilder.header("Transfer-Encoding", "chunked");
                //删除HTTP头部的Content-Length(内容长度,因为不是整体传输,所以内容长度不明确)
                requestBuilder.removeHeader("Content-Length");
            }
        }

        if (userRequest.header("Host") == null) {
           //设置HTTP头部的Host字段
            requestBuilder.header("Host", Util.hostHeader(userRequest.url(), false));
        }

        if (userRequest.header("Connection") == null) {
            //设置HTTP头部的Connection字段(连接类型,使用长连接)
            requestBuilder.header("Connection", "Keep-Alive");
        }

        boolean transparentGzip = false;
        if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
           //设置默认的压缩算法为gzip
            transparentGzip = true;
            requestBuilder.header("Accept-Encoding", "gzip");
        }
        
        //从本地加载本次请求路径对应的cookie列表
        List<Cookie> cookies = this.cookieJar.loadForRequest(userRequest.url());
        if (!cookies.isEmpty()) {
            //如果cookie列表存在
            //将cookie列表转变为格式化(key=val;key=val....)的字符串,并赋值给HTTP头部的Cookie字段
            requestBuilder.header("Cookie", this.cookieHeader(cookies));
        }

        if (userRequest.header("User-Agent") == null) {
            //设置默认的HTTP头部User-Agent
            requestBuilder.header("User-Agent", Version.userAgent());
        }
        //下一责任执行任务,以完成HTTP请求,最终返回HTTP响应对象
        Response networkResponse = chain.proceed(requestBuilder.build());
        //从响应对象的头部中读取Set-Cookie得到cookie列表,并进行保存
        HttpHeaders.receiveHeaders(this.cookieJar, userRequest.url(), networkResponse.headers());
        //重新构建响应Builder
        okhttp3.Response.Builder responseBuilder = networkResponse.newBuilder().request(userRequest);
        if (transparentGzip && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding")) && HttpHeaders.hasBody(networkResponse)) {
            //如果响应内容体是gzip压缩过的
            //构建压缩资源对象
            GzipSource responseBody = new GzipSource(networkResponse.body().source());
            //重构响应头部:移除Content-Encoding和Content-Length
            Headers strippedHeaders = networkResponse.headers().newBuilder().removeAll("Content-Encoding").removeAll("Content-Length").build();
            //设置响应头部为重构的头部
            responseBuilder.headers(strippedHeaders);
            //获取响应内容体的媒体类型
            String contentType = networkResponse.header("Content-Type");
            //解压缩响应内容,并赋值给重构的响应builder
            responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
        }
        //使用重构的响应builder构建新的响应对象,并返回
        return responseBuilder.build();
    }
    
    //将cookie列表,格式化(key=val;key=val....)为cookie字符串
    private String cookieHeader(List<Cookie> cookies) {
        StringBuilder cookieHeader = new StringBuilder();
        int i = 0;

        for(int size = cookies.size(); i < size; ++i) {
            if (i > 0) {
                cookieHeader.append("; ");
            }

            Cookie cookie = (Cookie)cookies.get(i);
            cookieHeader.append(cookie.name()).append('=').append(cookie.value());
        }

        return cookieHeader.toString();
    }
}

桥链拦截器的主要职责是:

  • 请求HTTP前,重新修正请求内容体的编码、压缩方式等
  • 请求HTTP前,读取本次请求路径对应的cookie并设置到HTTP请求头部的cookie字段
  • 请求HTTP后,得到响应对象,读取响应对象头部的的cookie列表并进行缓存
  • 请求HTTP后,得到响应对象,如果响应体内容是经过压缩的,则进行解压缩处理

请求HTTP前,桥链拦截器对从本地中读取Cookie是通过cookieJar 来实现的,而okhttp默认是NO_COOKIES(其读取和缓存都是空函数,不做任何处理),所以cookieJar.loadForRequest返回的是空。我看看NO_COOKIES的实现代码:

CookieJar NO_COOKIES = new CookieJar() {
    @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
         //将cookie列表保存到本地,不做任何处理
    }
    //从本地读取cookie列表
    @Override public List<Cookie> loadForRequest(HttpUrl url) {
      //返回空列表
      return Collections.emptyList();
    }
  };

请求HTTP后,得到响应对象,使用HttpHeaders.receiveHeaders方法从响应对象中的头部Set-Cookie获取cookie列表,再调用cookieJar将cookie列表存储在本地,okhttp默认是NO_COOKIES(其读取和缓存都是空函数,不做任何处理),所以cookieJar.saveFromResponse是不会做任何处理的,我们来看看HttpHeaders.receiveHeaders的代码:

public static void receiveHeaders(CookieJar cookieJar, HttpUrl url, Headers headers) {
    //如果cookie存取实现类是NO_COOKIES,则不做任何处理,直接返回
    if (cookieJar == CookieJar.NO_COOKIES) return;
    //从头部的Set-Cookie中读取并转义为Cookie列表
    List<Cookie> cookies = Cookie.parseAll(url, headers);
    if (cookies.isEmpty()) return;
    //调用saveFromResponse将cookie列表进行存储
    cookieJar.saveFromResponse(url, cookies);
  }

HttpHeaders.receiveHeaders先使用Cookie.parseAll解析set-cookie列表转为cookie对象列表,继而用cookieJar.saveFromResponse进行持久化存储到本地中。
我们看看Cookie.parseAll是怎么将set-cookie列表转为cookie对象列表的:

public static List<Cookie> parseAll(HttpUrl url, Headers headers) {
    //读取Set-Cookie字符串列表,每个Set-Cookie格式为:Set-Cookie: key=value[;Expires=Date Time][;Max-Age=second][;Secure][;HttpOnly][;SameSize=xxx][;Domain=xxx][;Path=xxx]
    List<String> cookieStrings = headers.values("Set-Cookie");
    List<Cookie> cookies = null;

    for (int i = 0, size = cookieStrings.size(); i < size; i++) {
      //遍历Set-Cookie字符串列表
      //将字符串按格式转为为Cookie对象
      Cookie cookie = Cookie.parse(url, cookieStrings.get(i));
      if (cookie == null) continue;
      if (cookies == null) cookies = new ArrayList<>();
      cookies.add(cookie);
    }

    return cookies != null
        ? Collections.unmodifiableList(cookies)
        : Collections.<Cookie>emptyList();
  }

Cookie.parseAll先从响应头中读取set-cookie字符串列表,再遍历字符串列表,使用Cookie.parse将每个set-cookie字符串按照规范格式解析为Cookie对象。
我们接着看Cookie.parse怎么按规范格式将set-cookie字符串解析为Cookie对象:

//set-cookie字符串值格式转为Cookie对象
public static @Nullable Cookie parse(HttpUrl url, String setCookie) {
    return parse(System.currentTimeMillis(), url, setCookie);
  }
  //set-cookie字符串值格式转为Cookie对象
 //currentTimeMillis:当前处理时间
//url:cookie对应的url
//set-cookie字符串格式
  static @Nullable Cookie parse(long currentTimeMillis, HttpUrl url, String setCookie) {
    //字符串读取开始位置
    int pos = 0;
    //set-cookie字符串值总长度
    int limit = setCookie.length();
    //从pos-limit 获取第一个“;”的位置,这个位置前面是key=value
    int cookiePairEnd = delimiterOffset(setCookie, pos, limit, ';');
    //从pos-cookiePairEnd得到key=value中=的位置
    int pairEqualsSign = delimiterOffset(setCookie, pos, cookiePairEnd, '=');
    //如果pairEqualsSign ==cookiePairEnd,表示没有cookie键值对,立即返回空
    if (pairEqualsSign == cookiePairEnd) return null;
    //从pos-pairEqualsSign,即key=value中=的前面获取cookie的名称
    String cookieName = trimSubstring(setCookie, pos, pairEqualsSign);
    //如果cookie名称是空的或者含非法字符(\n、空字符串、非Ascii等),直接返回空
    if (cookieName.isEmpty() || indexOfControlOrNonAscii(cookieName) != -1) return null;
    //从pairEqualsSign+1到cookiePairEnd,即key=value中=的后面获取cookie的值
    String cookieValue = trimSubstring(setCookie, pairEqualsSign + 1, cookiePairEnd);
    //如果cookie的值是空的或者含非法字符(\n、空字符串、非Ascii等),直接返回空
    if (indexOfControlOrNonAscii(cookieValue) != -1) return null;
    
    //cookie过期时间点,单位秒
    long expiresAt = HttpDate.MAX_DATE;
    //过期时间比较值
    long deltaSeconds = -1L;
    //cookie中设置的域名
    String domain = null;
    //cookie中设置的path
    String path = null;
    //cookie中是否仅仅https有效
    boolean secureOnly = false;
    //cookie中是否JavaScript的Document.cookie无法访问带有HttpOnly的Cookie的值
    boolean httpOnly = false;
    //cookie是否仅主域名有效
    boolean hostOnly = true;
    //cookie是否持久化
    boolean persistent = false;
    //将pos设置为key=value;之后的位置
    pos = cookiePairEnd + 1;
    while (pos < limit) {
      //遍历set-cookie值字符串,每次取“;”前面的name=value
      int attributePairEnd = delimiterOffset(setCookie, pos, limit, ';');

      int attributeEqualsSign = delimiterOffset(setCookie, pos, attributePairEnd, '=');
      //取得属性名
      String attributeName = trimSubstring(setCookie, pos, attributeEqualsSign);
      //取得属性值
      String attributeValue = attributeEqualsSign < attributePairEnd
          ? trimSubstring(setCookie, attributeEqualsSign + 1, attributePairEnd)
          : "";

      if (attributeName.equalsIgnoreCase("expires")) {
        //如果属性名是expires,即过期时间点
        try {
          //将过期时间点转为毫米数
          expiresAt = parseExpires(attributeValue, 0, attributeValue.length());
          //需持久化
          persistent = true;
        } catch (IllegalArgumentException e) {
          // Ignore this attribute, it isn't recognizable as a date.
        }
      } else if (attributeName.equalsIgnoreCase("max-age")) {
         //如果属性名是max-age,即距离过期时间剩余多少秒
        try {
          //过期剩余秒数
          deltaSeconds = parseMaxAge(attributeValue);
          //需持久化
          persistent = true;
        } catch (NumberFormatException e) {
          // Ignore this attribute, it isn't recognizable as a max age.
        }
      } else if (attributeName.equalsIgnoreCase("domain")) {
        //如果属性名是domain,即cookie有效的域名
        try {
          //解析得到域名
          domain = parseDomain(attributeValue);
          //主域名和子域名都有效
          hostOnly = false;
        } catch (IllegalArgumentException e) {
          // Ignore this attribute, it isn't recognizable as a domain.
        }
      } else if (attributeName.equalsIgnoreCase("path")) {
        //如果属性名是path,即cookie有效的路径
        path = attributeValue;
      } else if (attributeName.equalsIgnoreCase("secure")) {
        //如果属性名是secure,即cookie仅对https有效
        secureOnly = true;
      } else if (attributeName.equalsIgnoreCase("httponly")) {
        //如果属性名是httponly,即JavaScript的Document.cookie无法访问带有HttpOnly的Cookie的值
        httpOnly = true;
      }

      pos = attributePairEnd + 1;
    }

    // If 'Max-Age' is present, it takes precedence over 'Expires', regardless of the order the two
    // attributes are declared in the cookie string.
    //将剩余过期时间秒数转为具体过期时间点
    if (deltaSeconds == Long.MIN_VALUE) {
      expiresAt = Long.MIN_VALUE;
    } else if (deltaSeconds != -1L) {
      long deltaMilliseconds = deltaSeconds <= (Long.MAX_VALUE / 1000)
          ? deltaSeconds * 1000
          : Long.MAX_VALUE;
      expiresAt = currentTimeMillis + deltaMilliseconds;
      if (expiresAt < currentTimeMillis || expiresAt > HttpDate.MAX_DATE) {
        expiresAt = HttpDate.MAX_DATE; // Handle overflow & limit the date range.
      }
    }
    
    // If the domain is present, it must domain match. Otherwise we have a host-only cookie.
    String urlHost = url.host();
    if (domain == null) {  
      //set-cookie中没有domain,从url中获取
      domain = urlHost;
    } else if (!domainMatch(urlHost, domain)) {
      //set-cookie中的域名和url的域名不一致,表示从cookie是无效的,直接返回
      return null; // No domain match? This is either incompetence or malice!
    }

    // If the domain is a suffix of the url host, it must not be a public suffix.
    if (urlHost.length() != domain.length()
        && PublicSuffixDatabase.get().getEffectiveTldPlusOne(domain) == null) {
      //校验set-cookie的域名如果是子域名,他必须是不是公共的子域名
      return null;
    }

    // If the path is absent or didn't start with '/', use the default path. It's a string like
    // '/foo/bar' for a URL like 'http://example.com/foo/bar/baz'. It always starts with '/'.
    if (path == null || !path.startsWith("/")) {
      String encodedPath = url.encodedPath();
      int lastSlash = encodedPath.lastIndexOf('/');
      path = lastSlash != 0 ? encodedPath.substring(0, lastSlash) : "/";
    }
    //将解析的的cookie属性值,构建Cookie对象
    return new Cookie(cookieName, cookieValue, expiresAt, domain, path, secureOnly, httpOnly,
        hostOnly, persistent);
  }

set-cookie的规范格式:Set-Cookie: key=value[;Expires=Date Time][;Max-Age=second][;Secure][;HttpOnly][;SameSize=xxx][;Domain=xxx][;Path=xxx],所以Cookie.parse就是每次截取name=value;进行解析得到对应的cookie属性值,最后用这些属性值构建Cookie对象。

Cookie存取实现原理

在上述的源码解析中,cookie的本地读取和缓存均是经过CookieJar实现,而CookieJar只是一个接口,默认情况下Okhttp的cookie是一个CookieJar的内部类NO_COOKIES(其读取和缓存都是空函数,不做任何处理),即不使用cookie。鉴于此,我们借助第三方cookie实现库PersistentCookieJar进行讲解cookie是如何实现存取的。

public class PersistentCookieJar implements ClearableCookieJar {
    //内存中缓存的cookie列表
    private CookieCache cache;
    //cookie持久化实现类(保存到本地文件)
    private CookiePersistor persistor;

    public PersistentCookieJar(CookieCache cache, CookiePersistor persistor) {
        this.cache = cache;
        this.persistor = persistor;
        //从本地加载cookie列表,并缓存在内存中
        this.cache.addAll(persistor.loadAll());
    }
    
    //保存响应中的cookie
    @Override
    synchronized public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
        //将cookie列表缓存到内存中
        cache.addAll(cookies);
        //保存到文件中
        persistor.saveAll(filterPersistentCookies(cookies));
    }
    //过滤不需要保存的cookie
    private static List<Cookie> filterPersistentCookies(List<Cookie> cookies) {
        List<Cookie> persistentCookies = new ArrayList<>();

        for (Cookie cookie : cookies) {
           //变量cookie列表
            if (cookie.persistent()) {
                //如果cookie需要保存,则加入持久化列表中,等待持久化保存
                persistentCookies.add(cookie);
            }
        }
        return persistentCookies;
    }
    
    //跟url加载对应的cookie列表
    @Override
    synchronized public List<Cookie> loadForRequest(HttpUrl url) {
        List<Cookie> cookiesToRemove = new ArrayList<>();
        List<Cookie> validCookies = new ArrayList<>();

        for (Iterator<Cookie> it = cache.iterator(); it.hasNext(); ) {
            //变量内存中的cookie列表
            Cookie currentCookie = it.next();

            if (isCookieExpired(currentCookie)) {
                //如果cookie已过期
                //将cookie加入待删除列表中
                cookiesToRemove.add(currentCookie);
                //从内存中移除过期的cookie
                it.remove();

            } else if (currentCookie.matches(url)) {
                //如果cookie的路径和加载的路径匹配
                //将cookie
                validCookies.add(currentCookie);
            }
        }
        //从持久化中删除待删除的cookie列表
        persistor.removeAll(cookiesToRemove);
       //返回匹配的cookie列表
        return validCookies;
    }
    
    //判断cookie是否过期
    private static boolean isCookieExpired(Cookie cookie) {
        return cookie.expiresAt() < System.currentTimeMillis();
    }
    
    //清除内存中的cookie列表,并重新从持久化(文件)中加载到内存中
    @Override
    synchronized public void clearSession() {
        cache.clear();
        cache.addAll(persistor.loadAll());
    }
   
    //从内存和持久化中删除所有cookie
    @Override
    synchronized public void clear() {
        cache.clear();
        persistor.clear();
    }
}

PersistentCookieJar 是cookie加载、保存、删除的入口,真正的实现是CookiePersistor,CookiePersistor的实现类是SharedPrefsCookiePersistor ,我看看SharedPrefsCookiePersistor 源码:

public class SharedPrefsCookiePersistor implements CookiePersistor {
    //采用android的SharedPreferences 实现存储cookie
    private final SharedPreferences sharedPreferences;

    public SharedPrefsCookiePersistor(Context context) {
        //初始化sharedPreferences实例
        this(context.getSharedPreferences("CookiePersistence", Context.MODE_PRIVATE));
    }

    public SharedPrefsCookiePersistor(SharedPreferences sharedPreferences) {
        //初始化sharedPreferences实例
        this.sharedPreferences = sharedPreferences;
    }
    
    //从sharedPreferences 中加载所有的key-value
    @Override
    public List<Cookie> loadAll() {
        List<Cookie> cookies = new ArrayList<>(sharedPreferences.getAll().size());
       //sharedPreferences.getAll()返回Map<String, ?>
        for (Map.Entry<String, ?> entry : sharedPreferences.getAll().entrySet()) {
            //遍历所有sharedPreferences中的键值对
            //存储的cookie的字符串形式的值
            String serializedCookie = (String) entry.getValue();
            //将字符串使用序列化SerializableCookie解析为cookie对象
            Cookie cookie = new SerializableCookie().decode(serializedCookie);
            if (cookie != null) {
                cookies.add(cookie);
            }
        }
        return cookies;
    }
    
    //保存cookie列表到sharedPreferences中
    @Override
    public void saveAll(Collection<Cookie> cookies) {
        SharedPreferences.Editor editor = sharedPreferences.edit();
        for (Cookie cookie : cookies) {
           //键格式:[http/https] + "://" + domain + path + "|" + cookename
           //值:将对象序列化为字节流,在将字节流转为16进制的字符串
            editor.putString(createCookieKey(cookie), new SerializableCookie().encode(cookie));
        }
        editor.commit();
    }
    
    //从sharedPreferences中删除指定的cookie列表
    @Override
    public void removeAll(Collection<Cookie> cookies) {
        SharedPreferences.Editor editor = sharedPreferences.edit();
        for (Cookie cookie : cookies) {
            //遍历cookie列表
            //删除指定键的cookie
            editor.remove(createCookieKey(cookie));
        }
        editor.commit();
    }
    
    //生成cookie保存在sharedPreferences的键,格式为:[http/https] + "://" + domain + path + "|" + cookename
    private static String createCookieKey(Cookie cookie) {
        return (cookie.secure() ? "https" : "http") + "://" + cookie.domain() + cookie.path() + "|" + cookie.name();
    }
    
    //清空保存在sharedPreferences中的所有cookie
    @Override
    public void clear() {
        sharedPreferences.edit().clear().commit();
    }
}

PersistentCookieJar 采用SharedPreferences的方式,将cookie保存在sharedPreferences文件中,而保存时是将cookie序列化为字节流,再将字节流转为16进制的字符串;读取时再进行逆序处理解析得到Cookie对象(将16进制转为字节流,在将字节流反序列化为Cookie对象)。
我们看看序列化SerializableCookie是实现源码:

public class SerializableCookie implements Serializable {
    private static final String TAG = SerializableCookie.class.getSimpleName();

    private static final long serialVersionUID = -8594045714036645534L;

    private transient Cookie cookie;
    
    //将cookie序列化为字节流,再将字节流转为16进制的字符串
    public String encode(Cookie cookie) {
        this.cookie = cookie;
        //创建字节输出流
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = null;

        try {
            //使用字节输出流构建对象输出流
            objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            //将SerializableCookie写入对象输出流
            objectOutputStream.writeObject(this);
        } catch (IOException e) {
            Log.d(TAG, "IOException in encodeCookie", e);
            return null;
        } finally {
            if (objectOutputStream != null) {
                try {
                    // Closing a ByteArrayOutputStream has no effect, it can be used later (and is used in the return statement)
                    objectOutputStream.close();
                } catch (IOException e) {
                    Log.d(TAG, "Stream not closed in encodeCookie", e);
                }
            }
        }
        //提交对象输出流的字节流,再将字节流转为16进制的字符串
        return byteArrayToHexString(byteArrayOutputStream.toByteArray());
    }
    
    //将字节流转为16进制的字符串
    /**
     * Using some super basic byte array &lt;-&gt; hex conversions so we don't
     * have to rely on any large Base64 libraries. Can be overridden if you
     * like!
     *
     * @param bytes byte array to be converted
     * @return string containing hex values
     */
    private static String byteArrayToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        for (byte element : bytes) {
            //遍历字节流
            //将字节与0xff进行与操作,将高位置为0,保证正数
            int v = element & 0xff;
            if (v < 16) {
                //如果字节对应的int值小于16,在前面加0
                sb.append('0');
            }
            //字节对应的int值转为16进制
            sb.append(Integer.toHexString(v));
        }
        return sb.toString();
    }
    
    //将16进制转为字节流,再将字节流反序列化转为Cookie对象
    public Cookie decode(String encodedCookie) {
        //将16进制转为字节流
        byte[] bytes = hexStringToByteArray(encodedCookie);      
       //创建字节输入流
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(
                bytes);

        Cookie cookie = null;
        ObjectInputStream objectInputStream = null;
        try {
            //使用节输入流创建对象输入流
            objectInputStream = new ObjectInputStream(byteArrayInputStream);
            //使用对象输入流反序列化得到Cookie对象
            cookie = ((SerializableCookie) objectInputStream.readObject()).cookie;
        } catch (IOException e) {
            Log.d(TAG, "IOException in decodeCookie", e);
        } catch (ClassNotFoundException e) {
            Log.d(TAG, "ClassNotFoundException in decodeCookie", e);
        } finally {
            if (objectInputStream != null) {
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    Log.d(TAG, "Stream not closed in decodeCookie", e);
                }
            }
        }
        return cookie;
    }
    
    //将16进制字符串转为字节流
    /**
     * Converts hex values from strings to byte array
     *
     * @param hexString string of hex-encoded values
     * @return decoded byte array
     */
    private static byte[] hexStringToByteArray(String hexString) {
        //字符串长度
        int len = hexString.length();
        //字节流长度
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            //遍历字符串每两个字符
            //将2个16进制字符转为字节
            data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character
                    .digit(hexString.charAt(i + 1), 16));
        }
        return data;
    }

    private static long NON_VALID_EXPIRES_AT = -1L;
    //序列化对象时,回调此方法将对象实例的cookie属性写入对象输出流中
    private void writeObject(ObjectOutputStream out) throws IOException {
        //将Cookie的属性写入对象输出流汇总
        out.writeObject(cookie.name());
        out.writeObject(cookie.value());
        out.writeLong(cookie.persistent() ? cookie.expiresAt() : NON_VALID_EXPIRES_AT);
        out.writeObject(cookie.domain());
        out.writeObject(cookie.path());
        out.writeBoolean(cookie.secure());
        out.writeBoolean(cookie.httpOnly());
        out.writeBoolean(cookie.hostOnly());
    }
    //反序列化对象时,回调此方法,从对象输入流读取cookie相关属性
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        //构建Cookie  Builder对象
        Cookie.Builder builder = new Cookie.Builder();
        //按序列化时的顺序,从对象输入流中读取Cookie的属性
        builder.name((String) in.readObject());

        builder.value((String) in.readObject());

        long expiresAt = in.readLong();
        if (expiresAt != NON_VALID_EXPIRES_AT) {
            builder.expiresAt(expiresAt);
        }

        final String domain = (String) in.readObject();
        builder.domain(domain);

        builder.path((String) in.readObject());

        if (in.readBoolean())
            builder.secure();

        if (in.readBoolean())
            builder.httpOnly();

        if (in.readBoolean())
            builder.hostOnlyDomain(domain);

        cookie = builder.build();
    }

}

SerializableCookie是Cookie序列化和反序列化的实现类,序列化是将Cookie的属性写入对象输入流中,以此得到Cookie对象的字节流,再将字节流转为16进制的字符串,作为保存在SharedPreferences中的值;反序列化是将16进制字符串先转为字节流,在将字节流反序列化(从字节流中按序列化时的顺序读取Cookie的属性)为Cookie对象。

总结

okhttp中cookie的操作是通过桥链拦截器BridgeInterceptor中借助CookieJar实现的,在请求前从CookieJar中读取url对应的cookie赋值到请求头部的Cookie字段;请求得到响应后,先从响应头中将set-cookie字符串列表转换为cookie列表,在使用CookieJar进行持久化存储。而okhttp默认情况下CookieJar是不做任何处理的,如果真需要实现Cookie的存取操作,可以自己实现CookieJar自行实现存取逻辑,也可以使用三方可如PersistentCookieJar实现存储。(cookie的存储最终是用什么方式存储可自行考量:直接文件、SharedPreferences、sqlite等)

相关文章

网友评论

    本文标题:okhttp原理解析之cookie

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