教你认清HttpURLConnection里面的CookieManager,CookieStore,CookieHandler,HttpCookie,CookiePolicy


一.结构大纲。


关于HttpURLConnection的一些用法,相信大家都很熟练了,如果你不想用网上一些网络框架,不凡可以自己封装下HttpURLConnection,自己封装自己用感觉还是不错的。好了回到正题,大家访问服务器的话,要想保持连接,必然要涉及到Cookie的保存,而在网络请求中,谷歌爸爸早已封装了一些类来帮助我们进行Cookie的保存。现在我们来认识一下CookieManager,CookieStore,CookieHandler,HttpCookie,CookiePolicy 他们之间的区别,先看一张Visio图,


二.结构解析。


可以看到CookieHandler是一个抽象类,里面封装了用于保存Cookie获取Cookie的一系列抽象方法;而CookieManager则是CookieHandler的实现类,继承了CookieHandler,也就是CookieManager里面实现了保存Cookie获取Cookie的一系列方法,至于怎么实现,我们等下再来看源码,继续看他们的结构。CookieManager的构造方法出现了两个类分别是CookieStore 和 CookiePolicy,那么这两个类又是什么呢?可以看到CookieStore是用来存储Cookie的。这里要注意下区别,CookieManager是实现了保存Cookie获取Cookie的方法,而CookieStore是用来存储Cookie的,实际上,CookieManager保存Cookie就是通过CookieStore来保存的,它里面有CookieStore,后面源码会介绍到。继续看,知道了CookieStore是用来存储Cookie,但是它里面怎么有个HttpCookie的类?我们可以大胆猜测这个一个封装Cookie的类,确实是这样,HttpCookie根据请求的响应头,从set-cookie请求头中取出Cookie,然后拆分出name,path,domain(域名),value,集合成一起的一个类。而至于CookiePolicy,他代表的是一种Cookie保存政策,里面提供三种字段,来确定当前Cookie是否应该接受还是拒绝。可以看到CookieManager是一个采取一定的Cookie保存政策的一个Cookie管理者(没有设置CookiePolicy的话会有默认值),内部是通过CookieStore来存储Cookie。

三.源码解析。


首先先来看CookieManager
我们来看下它的源码结构:
public class CookieManager extends CookieHandler {
    private CookieStore store;

    private CookiePolicy policy;

    private static final String VERSION_ZERO_HEADER = "Set-cookie";

    private static final String VERSION_ONE_HEADER = "Set-cookie2";

     ....
}
首先从成员变量上看,CookieManager内部带有两个成员变量CookieStore,CookiePolicy,而下面两个常量Set-Cookie,想必就不用说了吧,很明显是Cookie的返回请求头,Set-cookie是cookie版本version为0的请求头,而Set-cookie2是cookie版本version为1的请求头,可以自行谷歌。
我们来看他的关键代码,也就是那个put(URI uri , Map<String , List<String>> responseHeaders)
 @Override
    public void put(URI uri, Map<String, List<String>> responseHeaders) throws IOException {
        ...//省略无关的代码
        // parse and construct cookies according to the map
        List<HttpCookie> cookies = parseCookie(responseHeaders);
        for (HttpCookie cookie : cookies) {

            // if the cookie doesn't have a domain, set one. The policy will do validation.
            if (cookie.getDomain() == null) {
                cookie.setDomain(uri.getHost());
            }

            // if the cookie doesn't have a path, set one. If it does, validate it.
            if (cookie.getPath() == null) {
                cookie.setPath(pathToCookiePath(uri.getPath()));
            } else if (!HttpCookie.pathMatches(cookie, uri)) {
                continue;
            }

            // if the cookie has the placeholder port list "", set the port. Otherwise validate it.
            if ("".equals(cookie.getPortlist())) {
                cookie.setPortlist(Integer.toString(uri.getEffectivePort()));
            } else if (cookie.getPortlist() != null && !HttpCookie.portMatches(cookie, uri)) {
                continue;
            }

            // if the cookie conforms to the policy, add it into the store
            if (policy.shouldAccept(uri, cookie)) {
                store.add(uri, cookie);
            }
        }
    }

这里调用到了parseCookie(responseHeaders)这个方法,至于Map<String ,List<String>> responseHeader , 这个参数,大家看着看着是不是感觉很熟悉,没错就是 httpUrlConnection.getHeaderFields() 的返回值,该方法是返回响应头的所有头字段和值,当然我们只要Cookie的话只需要找到Set-cookie/Set-cookie2字段,我们看下这个parseCookie方法
private static List<HttpCookie> parseCookie(Map<String, List<String>> responseHeaders) {
        List<HttpCookie> cookies = new ArrayList<HttpCookie>();
        for (Map.Entry<String, List<String>> entry : responseHeaders.entrySet()) {
            String key = entry.getKey();
            // Only "Set-cookie" and "Set-cookie2" pair will be parsed
            if (key != null && (key.equalsIgnoreCase(VERSION_ZERO_HEADER)
                    || key.equalsIgnoreCase(VERSION_ONE_HEADER))) {
                // parse list elements one by one
                for (String cookieStr : entry.getValue()) {
                    try {
                        for (HttpCookie cookie : HttpCookie.parse(cookieStr)) {
                            cookies.add(cookie);
                        }
                    } catch (IllegalArgumentException ignored) {
                        // this string is invalid, jump to the next one.
                    }
                }
            }
        }
        return cookies;
    }
果然没错,是 根据VERSION_ZERO_HEADER(Set-cookie)来判断的,那么既然他是根据Set-cookie来找出Cookie的,它是怎么解析出来的呢?来,我们看下他这个
for (HttpCookie cookie : HttpCookie.parse(cookieStr)) {
                            cookies.add(cookie);
                        }
点进去
  public static List<HttpCookie> parse(String header) {
        return new CookieParser(header).parse();
    }
再点击那个parse进去看,可以看到
 public List<HttpCookie> parse() {
            List<HttpCookie> cookies = new ArrayList<HttpCookie>(2);

            // The RI permits input without either the "Set-Cookie:" or "Set-Cookie2" headers.
            boolean pre2965 = true;
            if (inputLowerCase.startsWith("set-cookie2:")) {
                pos += "set-cookie2:".length();
                pre2965 = false;
                hasVersion = true;
            } else if (inputLowerCase.startsWith("set-cookie:")) {
                pos += "set-cookie:".length();
            }

            ...//省略无关代码    
                String value = readAttributeValue(pre2965 ? ";" : ",;");
                HttpCookie cookie = new HttpCookie(name, value);
                cookie.version = pre2965 ? 0 : 1;
                cookies.add(cookie);
}
可以看到,跟我们猜想的一样,HttpCookie.parse(cookieStr)中是根据Set-cookie字段来拆分Cookie的,拆分出name,value,path,domain等然后合并成一个List<HttpCookie>集合,每一个HttpCookie就代表了一个Cookie信息。
好,回到刚刚CookieManager的put方法上,put方法里面调用了
   List<HttpCookie> cookies = parseCookie(responseHeaders);
这个方法解析出响应头字段中的cookie信息,但是怎么还没保存呢?别急,继续看下去,可以看到CookieStore终于出场了
// if the cookie conforms to the policy, add it into the store
            if (policy.shouldAccept(uri, cookie)) {
                store.add(uri, cookie);
            }
它先根据CookiePolicy的规则判断当前cookie是否要接受保存,如果是则添加到CookieStore里。到这里相比大家都有点豁然开朗的感觉吧。没错,CookieManager内部是采用CookiePolicy来定制cookie保存规则,而真正保存是使用CookieStore来保存的,至于怎么保存,大家可以自行阅读源码啦。相信大家对CookieHandler,CookieManger,CookieStore,CookiePolicy,HttpCookie已经有了大概的了解了吧,既然知道原理,那还等什么,赶紧实践下


四.Cookie实践。


eg:
CookieManager cookieManager = new CookieManager();
//这个方法就可以把Cookie保存在CookieStore里面了
cookieManager.put(new URI(url) , httpURLConnection.getHeaderFields());
//获取CookieStore
CookieStore cookieStore = cookieManager.getCookieStore();
//再拿出里面的HttpCookie
 for (HttpCookie httpCookie : cookieStore.getCookies()) {
                    Log.e(TAG, "httpCookie: domain  " + httpCookie.getDomain());
                    Log.e(TAG, "httpCookie: value  " + httpCookie.getValue());
                    Log.e(TAG, "httpCookie: path  " + httpCookie.getPath());
                    Log.e(TAG, "httpCookie: name  " + httpCookie.getName());
                    Log.e(TAG, "httpCookie: toString  " + httpCookie.toString());
                }

我们来看下打印数据
09-24 15:14:06.573 /com.rdc.zzh.networkutil E/NetworkUtil: httpCookie: domain  .baidu.com
09-24 15:14:06.573 /com.rdc.zzh.networkutil E/NetworkUtil: httpCookie: value  172DB173E657AEA43F99877C03311B50:FG=1
09-24 15:14:06.573 /com.rdc.zzh.networkutil E/NetworkUtil: httpCookie: path  /
09-24 15:14:06.573 /com.rdc.zzh.networkutil E/NetworkUtil: httpCookie: name  BAIDUID
09-24 15:14:06.573 /com.rdc.zzh.networkutil E/NetworkUtil: httpCookie: toString  BAIDUID=172DB173E657AEA43F99877C03311B50:FG=1

提醒下,这里httpCookie.toString()接口就是sessionId的值哦,也就是直接把这个值方法请求头"Cookie"中就行。
好了,我们来总结下:


五.总结。


①CookieHandler是抽象类,而CookieManager是CookieHandler的实现类,内存采用 CookiePolicy来定制cookie保存规则,而真正保存是使用CookieStore来保存的
②HttpCookie是Cookie的载体,带有Cookie的name,path,value,domain值
③CookiePolicy是Cookie保存规则,来决定当前Cookie是否要保存
④CookieStore则是用来存储Cookie的,也就是存储HttpCookie。

好了,到这里大家已经知道了要如何去保存Cookie了吧,也就可以自己尝试去封装HttpUrlConnection了。

这是我自己简单封装了HttpURLConnection方法
有:

* 封装了get和post方法的请求
* 请求参数的封装PostBody
* 设置了Cookie的自动保存
* 获取过程中可以选择是否带进度监听


 NetworkUtil.getInstance().get("http://www.baidu.com", new ResultListener() {
         @Override
         public void onResultSuccess(String success) {
            Log.e(TAG, "onResultSuccess: " + success);
         }

        @Override
        public void onResultFail(String fail) {
            Log.e(TAG, "onResultFail: " + fail);
        }
    });


感兴趣的可以看下。

参考资料:




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值