第六章 高級(jí)主題
6.1 自定義客戶端連接
在特定條件下,也許需要來定制HTTP報(bào)文通過線路傳遞,越過了可能使用的HTTP參數(shù)來處理非標(biāo)準(zhǔn)不兼容行為的方式。比如,對(duì)于Web爬蟲,它可能需要強(qiáng)制HttpClient接受格式錯(cuò)誤的響應(yīng)頭部信息,來搶救報(bào)文的內(nèi)容。
通常插入一個(gè)自定義的報(bào)文解析器的過程或定制連接實(shí)現(xiàn)需要幾個(gè)步驟:
提供一個(gè)自定義LineParser/LineFormatter接口實(shí)現(xiàn)。如果需要,實(shí)現(xiàn)報(bào)文解析/格式化邏輯。
class MyLineParser extends BasicLineParser {
@Override
public Header parseHeader(
final CharArrayBuffer buffer) throws ParseException {
try {
return super.parseHeader(buffer);
} catch (ParseException ex) {
// 壓制ParseException異常
return new BasicHeader("invalid", buffer.toString());
}
}
}
提過一個(gè)自定義的OperatedClientConnection實(shí)現(xiàn)。替換需要自定義的默認(rèn)請(qǐng)求/響應(yīng)解析器,請(qǐng)求/響應(yīng)格式化器。如果需要,實(shí)現(xiàn)不同的報(bào)文寫入/讀取代碼。
class MyClientConnection extends DefaultClientConnection {
@Override
protected HttpMessageParser createResponseParser(
final SessionInputBuffer buffer,
final HttpResponseFactory responseFactory,
final HttpParams params) {
return new DefaultResponseParser(buffer,
new MyLineParser(),responseFactory,params);
}
}
為了創(chuàng)建新類的連接,提供一個(gè)自定義的ClientConnectionOperator接口實(shí)現(xiàn)。如果需要,實(shí)現(xiàn)不同的套接字初始化代碼。
class MyClientConnectionOperator extends
DefaultClientConnectionOperator {
public MyClientConnectionOperator(
final SchemeRegistry sr) {
super(sr);
}
@Override
public OperatedClientConnection createConnection() {
return new MyClientConnection();
}
}
為了創(chuàng)建新類的連接操作,提供自定義的ClientConnectionManager接口實(shí)現(xiàn)。
class MyClientConnManager extends SingleClientConnManager {
public MyClientConnManager(
final HttpParams params,
final SchemeRegistry sr) {
super(params, sr);
}
@Override
protected ClientConnectionOperator createConnectionOperator(
final SchemeRegistry sr) {
return new MyClientConnectionOperator(sr);
}
}
6.2 有狀態(tài)的HTTP連接
HTTP規(guī)范假設(shè)session狀態(tài)信息通常是以HTTP cookie格式嵌入在HTTP報(bào)文中的,因此HTTP連接通常是無狀態(tài)的,這個(gè)假設(shè)在現(xiàn)實(shí)生活中通常是不對(duì)的。也有一些情況,當(dāng)HTTP連接使用特定的用戶標(biāo)識(shí)或特定的安全上下文來創(chuàng)建時(shí),因此不能和其它用戶共享,只能由該用戶重用。這樣的有狀態(tài)的HTTP連接的示例就是NTLM認(rèn)證連接和使用客戶端證書認(rèn)證的SSL連接。
6.2.1 用戶令牌處理器
HttpClient依賴UserTokenHandler接口來決定給定的執(zhí)行上下文是否是用戶指定的。如果這個(gè)上下文是用戶指定的或者如果上下文沒有包含任何資源或關(guān)于當(dāng)前用戶指定詳情而是null,令牌對(duì)象由這個(gè)處理器返回,期望唯一地標(biāo)識(shí)當(dāng)前的用戶。用戶令牌將被用來保證用戶指定資源不會(huì)和其它用戶來共享或重用。
如果它可以從給定的執(zhí)行上下文中來獲得,UserTokenHandler接口的默認(rèn)實(shí)現(xiàn)是使用主類的一個(gè)實(shí)例來代表HTTP連接的狀態(tài)對(duì)象。UserTokenHandler將會(huì)使用基于如NTLM或開啟的客戶端認(rèn)證SSL會(huì)話認(rèn)證模式的用戶的主連接。如果二者都不可用,那么就不會(huì)返回令牌。
如果默認(rèn)的不能滿足它們的需要,用戶可以提供一個(gè)自定義的實(shí)現(xiàn):
DefaultHttpClient httpclient = new DefaultHttpClient();
httpclient.setUserTokenHandler(new UserTokenHandler() {
public Object getUserToken(HttpContext context) {
return context.getAttribute("my-token");
}
});
6.2.2 用戶令牌和執(zhí)行上下文
在HTTP請(qǐng)求執(zhí)行的過程中,HttpClient添加了下列和用戶標(biāo)識(shí)相關(guān)的對(duì)象到執(zhí)行上下文中:
'http.user-token':對(duì)象實(shí)例代表真實(shí)的用戶標(biāo)識(shí),通常期望Principle接口的實(shí)例。
我們可以在請(qǐng)求被執(zhí)行后,通過檢查本地HTTP上下文的內(nèi)容,發(fā)現(xiàn)是否用于執(zhí)行請(qǐng)求的連接是有狀態(tài)的。
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpContext localContext = new BasicHttpContext();
HttpGet httpget = new HttpGet("http://localhost:8080/");
HttpResponse response = httpclient.execute(httpget, localContext);
HttpEntity entity = response.getEntity();
if (entity != null) {
entity.consumeContent();
}
Object userToken = localContext.getAttribute(ClientContext.USER_TOKEN);
System.out.println(userToken);
6.2.2.1 持久化有狀態(tài)的連接
請(qǐng)注意帶有狀態(tài)對(duì)象的持久化連接僅當(dāng)請(qǐng)求被執(zhí)行時(shí),相同狀態(tài)對(duì)象被綁定到執(zhí)行上下文時(shí)可以被重用。所以,保證相同上下文重用于執(zhí)行隨后的相同用戶,或用戶令牌綁定到之前請(qǐng)求執(zhí)行上下文的HTTP請(qǐng)求是很重要的。
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpContext localContext1 = new BasicHttpContext();
HttpGet httpget1 = new HttpGet("http://localhost:8080/");
HttpResponse response1 = httpclient.execute(httpget1, localContext1);
HttpEntity entity1 = response1.getEntity();
if (entity1 != null) {
entity1.consumeContent();
}
Principal principal = (Principal) localContext1.getAttribute(
ClientContext.USER_TOKEN);
HttpContext localContext2 = new BasicHttpContext();
localContext2.setAttribute(ClientContext.USER_TOKEN, principal);
HttpGet httpget2 = new HttpGet("http://localhost:8080/");
HttpResponse response2 = httpclient.execute(httpget2, localContext2);
HttpEntity entity2 = response2.getEntity();
if (entity2 != null) {
entity2.consumeContent();
}