前言
HttpClient已经被集成到Android的SDK里,但在JDK里面仍然需要HttpURLConnectionn发起HTTP请求。HttpClient可以看做是一个加强版的HttpURLConnection,但它的侧重点是如何发送请求、接受相应和管理Http连接。
在介绍Http Cookies之前,笔者给出一个应用场景:你需要一个根据地理信息(城市名或者经纬度)获取天气的应用。可选的API很多,不幸的是,网上提到的Google天气API已经停止服务了(不是被墙);雅虎是英文的,且需要得到其城市ID;其他各种知名或不知名的要么收费,要么不好用。实际上,百度推出的车联网API也支持天气服务,不过每个申请的AK原则上访问次数有限(真要做天气应用,推荐使用中国国家气象局的API)。当在百度你申请了AK,发出访问时,可能有这样的提示:
其基本代码如下:
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet get = new HttpGet("http://api.map.baidu.com/telematics/v3/weather?location=西安&output=json&ak=yourkey");
CloseableHttpResponse response = httpClient.execute(get);
System.out.println(EntityUtils.toString(response.getEntity()));
其实,做个爬虫的朋友大多遇到这个警告:
Cookie rejected。
好吧。Cookie rejected,下面我们详细讨论HttpClient的Cookie管理机制。
什么是Cookie
一个Http Cookie就是一个令牌或者状态信息的短包,用来在Http代理和目标服务器间维护一个session交互信息。这个名字最早来自于Netscape的工程师。
Httpclient使用Cookie的接口来表示抽象的cookie令牌。它最简单的形式是一个键值对。通常一个Http cookie也包含大量属性,比如版本号,合法的域,在原始服务器上的该cooki起作用的子集URL的路径,cookie存活的最大时间等。
SetCookie接口表示一个由服务器发给Http代理的Set-Cookie响应头,用以维护会话状态。SetCookie2接口继承了SetCookie,并有特定的Set-Cookie2方法。
ClientCookie接口继承了Cookie接口,并添加了额外的特殊功能,比如获取原始的Cookie属性。这对产生Cookie头很重要,因为一些cookie说明要求Cookie头包含只在Set-Cookie或Set-Cookie指定的特定属性。
Cookie版本号
与Netscape草案兼容但不和官方规范兼容的被看做是版本0,标准的兼容cookie被看做是版本1。HttpClient根据版本号可能会以不同方式处理cookie。
下面是一个标准cookie重建的例子。注意:标准兼容cookie必须保留所有来自原始服务器的属性。
BasicClientCookie2 stdCookie = new BasicClientCookie2("name", "value");
stdCookie.setVersion(1);
stdCookie.setDomain(".baidu.com");
stdCookie.setPorts(new int[] {80,8080});
stdCookie.setPath("/");
stdCookie.setSecure(true);
stdCookie.setAttribute(ClientCookie.VERSION_ATTR, "1");
stdCookie.setAttribute(ClientCookie.DOMAIN_ATTR, ".baidu.com");
stdCookie.setAttribute(ClientCookie.PORT_ATTR, "80,8080");
Cookie规范
CookieSpe接口表示一个Cookie管理规范。这个规范有:
- Set-Cookie的传递规则和可选的Set-Cookie2头信息
- 已传递cookie的验证
- 对所给主机、端口和路径的Cookie头的形式
下面有几种Cookie的实现:
Netscape draft:由Netscape委员会发布的原始草案。除非为了绝对的兼容,请避免使用。
Standard:RFC 2965 HTTP 状态管理规范。
Browser compatibility:该实现竭力减小常见浏览器的差异性。
Best match:“元”cookie规范,依据Http响应cookie的格式得到一个cookie规范。基本上集合了上面的所有。
Ignore cookies:忽略所有。
强烈建议使用Best match。
选择cookie 策略
cookie策略可以在Http客户端设置,并在必要时可在Http requset级别上重写。
RequestConfig globalConfig = RequestConfig.custom()
.setCookieSpec(CookieSpecs.BEST_MATCH)
.build();
CloseableHttpClient httpclient = HttpClients.custom()
.setDefaultRequestConfig(globalConfig)
.build();
RequestConfig localConfig = RequestConfig.copy(globalConfig)
.setCookieSpec(CookieSpecs.BROWSER_COMPATIBILITY)
.build();
HttpGet httpGet = new HttpGet("/");
httpGet.setConfig(localConfig);
自定义cookie策略
为了实现一个定制的cookie策略,需要实现CookieSpec接口,创建一个CookieSpeProvier的实现来创建和初始化自定义规范并进行注册。一旦定制的规范完成了注册,它可以像标准的cookie规范一样被激活。
CookieSpecProvider easySpecProvider = new CookieSpecProvider() {
public CookieSpec create(HttpContext context) {
return new BrowserCompatSpec() {
@Override
public void validate(Cookie cookie, CookieOrigin origin)
throws MalformedCookieException {
// Oh, I am easy
}
};
}
};
Registry<CookieSpecProvider> r = RegistryBuilder.<CookieSpecProvider>create()
.register(CookieSpecs.BEST_MATCH,
new BestMatchSpecFactory())
.register(CookieSpecs.BROWSER_COMPATIBILITY,
new BrowserCompatSpecFactory())
.register("easy", easySpecProvider)
.build();
RequestConfig requestConfig = RequestConfig.custom()
.setCookieSpec("easy")
.build();
CloseableHttpClient httpclient = HttpClients.custom()
.setDefaultCookieSpecRegistry(r)
.setDefaultRequestConfig(requestConfig)
.build();
Cookie的持久化
HttpClien可以和任何实现CookieStrore接口的持久化cookie store的物理表示协作。默认的CookieStroe实现是BasicCookie,它是用一个ArrayList实现的。不幸的是,当垃圾回收时存储在BasicClientCookie对象的数据会丢失。
// Create a local instance of cookie store
CookieStore cookieStore = new BasicCookieStore();
// Populate cookies if needed
BasicClientCookie cookie = new BasicClientCookie("name", "value");
cookie.setVersion(0);
cookie.setDomain(".mycompany.com");
cookie.setPath("/");
cookieStore.addCookie(cookie);
// Set the store
CloseableHttpClient httpclient = HttpClients.custom()
.setDefaultCookieStore(cookieStore)
.build();
RequestConfig globalConfig = RequestConfig.custom().
setCookieSpec(CookieSpecs.BEST_MATCH).build();
CloseableHttpClient client = HttpClients.custom().
setDefaultRequestConfig(globalConfig).build();
RequestConfig localConfig = RequestConfig.copy(globalConfig)
.setCookieSpec(CookieSpecs.BROWSER_COMPATIBILITY)
.build();
HttpGet get = new HttpGet(sb.toString());
get.setConfig(localConfig);
CloseableHttpResponse response = client.execute(get);
String weatherDetail = EntityUtils.toString(response.getEntity());
返回结果: