6.1 自定義客戶端連接
在特定條件下,也許需要來定制HTTP報文通過線路傳遞,越過了可能使用的HTTP參數來處理非標準不兼容行為的方式。比如,對于Web爬蟲,它可能需要強制HttpClient接受格式錯誤的響應頭部信息,來搶救報文的內容。
通常插入一個自定義的報文解析器的過程或定制連接實現需要幾個步驟:
提供一個自定義LineParser/LineFormatter接口實現。如果需要,實現報文解析/格式化邏輯。
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());
}
}
}
提過一個自定義的OperatedClientConnection實現。替換需要自定義的默認請求/響應解析器,請求/響應格式化器。如果需要,實現不同的報文寫入/讀取代碼。
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);
}
}
為了創建新類的連接,提供一個自定義的ClientConnectionOperator接口實現。如果需要,實現不同的套接字初始化代碼。
class MyClientConnectionOperator extends
DefaultClientConnectionOperator {
public MyClientConnectionOperator(
final SchemeRegistry sr) {
super(sr);
}
@Override
public OperatedClientConnection createConnection() {
return new MyClientConnection();
}
}
為了創建新類的連接操作,提供自定義的ClientConnectionManager接口實現。
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 有狀態的HTTP連接
HTTP規范假設session狀態信息通常是以HTTP cookie格式嵌入在HTTP報文中的,因此HTTP連接通常是無狀態的,這個假設在現實生活中通常是不對的。也有一些情況,當HTTP連接使用特定的用戶標識或特定的安全上下文來創建時,因此不能和其它用戶共享,只能由該用戶重用。這樣的有狀態的HTTP連接的示例就是NTLM認證連接和使用客戶端證書認證的SSL連接。
6.2.1 用戶令牌處理器
HttpClient依賴UserTokenHandler接口來決定給定的執行上下文是否是用戶指定的。如果這個上下文是用戶指定的或者如果上下文沒有包含任何資源或關于當前用戶指定詳情而是null,令牌對象由這個處理器返回,期望唯一地標識當前的用戶。用戶令牌將被用來保證用戶指定資源不會和其它用戶來共享或重用。
如果它可以從給定的執行上下文中來獲得,UserTokenHandler接口的默認實現是使用主類的一個實例來代表HTTP連接的狀態對象。UserTokenHandler將會使用基于如NTLM或開啟的客戶端認證SSL會話認證模式的用戶的主連接。如果二者都不可用,那么就不會返回令牌。
如果默認的不能滿足它們的需要,用戶可以提供一個自定義的實現:
DefaultHttpClient httpclient = new DefaultHttpClient();
httpclient.setUserTokenHandler(new UserTokenHandler() {
public Object getUserToken(HttpContext context) {
return context.getAttribute("my-token");
}
});
6.2.2 用戶令牌和執行上下文
在HTTP請求執行的過程中,HttpClient添加了下列和用戶標識相關的對象到執行上下文中:
'http.user-token':對象實例代表真實的用戶標識,通常期望Principle接口的實例。
我們可以在請求被執行后,通過檢查本地HTTP上下文的內容,發現是否用于執行請求的連接是有狀態的。
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 持久化有狀態的連接
請注意帶有狀態對象的持久化連接僅當請求被執行時,相同狀態對象被綁定到執行上下文時可以被重用。所以,保證相同上下文重用于執行隨后的相同用戶,或用戶令牌綁定到之前請求執行上下文的HTTP請求是很重要的。
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();
}