前言
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());
返回結果: