您的位置:首页 > 理论基础 > 计算机网络

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

2016-09-24 14:24 387 查看



一.结构大纲。

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


地址: https://github.com/ZengZeHong/NetworkUtil
感兴趣的可以看下。

参考资料:
谷歌官方API:https://developer.android.com/reference/java/net/CookieManager.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息