前言
尽管java.net包提供了基本通过HTTP访问资源的功能,但它没有提供全面的灵活性和其它很多应用程序需要的功能。HttpClient就是寻求弥补这项空白的组件,通过提供一个有效的,保持更新的,功能丰富的软件包来实现客户端最新的HTTP标准和建议。
1. HttpClient的范围
? 基于HttpCore[http://hc.apache.org/httpcomponents-core/index.html]的客户端HTTP运输实现库
? 基于经典(阻塞)I/O
? 内容无关
2. 什么是HttpClient不能做的
? HttpClient不是一个浏览器。
第一章 基础
1.1 执行请求
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/");
CloseableHttpResponse response = httpclient.execute(httpget);
try {
<...>
} finally {
response.close();
}
1.1.1 HTTP请求
HttpClient支持所有定义在HTTP/1.1版本中的HTTP方法:GET,HEAD,POST,PUT,DELETE,TRACE和OPTIONS。对于每个方法类型都有一个特殊的类:HttpGet,HttpHead,HttpPost,HttpPut,HttpDelete,HttpTrace和HttpOptions。
请求的URI是统一资源定位符,它标识了应用于哪个请求之上的资源。HTTP请求URI包含一个协议模式,主机名称,可选的端口,资源路径,可选的查询和可选的片段。
URI uri = new URIBuilder()
.setScheme("http")
.setHost("www.google.com")
.setPath("/search")
.setParameter("q", "httpclient")
.setParameter("btnG", "Google Search")
.setParameter("aq", "f")
.setParameter("oq", "")
.build();
HttpGet httpget = new HttpGet(uri);
System.out.println(httpget.getURI());
1.1.2 HTTP响应
HTTP响应是由服务器在接收和解释请求报文之后返回发送给客户端的报文。响应报文的第一行包含了协议版本,之后是数字状态码和相关联的文本段。
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
System.out.println(response.getProtocolVersion());
System.out.println(response.getStatusLine().getStatusCode());
System.out.println(response.getStatusLine().getReasonPhrase());
System.out.println(response.getStatusLine().toString());
1.1.3 处理报文头部
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
response.addHeader("Set-Cookie", "c1=a; path=/; domain=localhost");
response.addHeader("Set-Cookie", "c2=b; path=\"/\", c3=c; domain=\"localhost\"");
Header h1 = response.getFirstHeader("Set-Cookie");
System.out.println(h1);
Header h2 = response.getLastHeader("Set-Cookie");
System.out.println(h2);
Header[] hs = response.getHeaders("Set-Cookie");
System.out.println(hs.length);
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
response.addHeader("Set-Cookie", "c1=a; path=/; domain=localhost");
response.addHeader("Set-Cookie", "c2=b; path=\"/\", c3=c; domain=\"localhost\"");
HeaderIterator it = response.headerIterator("Set-Cookie");
while (it.hasNext()) {
System.out.println(it.next());
}
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
response.addHeader("Set-Cookie", "c1=a; path=/; domain=localhost");
response.addHeader("Set-Cookie", "c2=b; path=\"/\", c3=c; domain=\"localhost\"");
HeaderElementIterator it = new BasicHeaderElementIterator(
response.headerIterator("Set-Cookie"));
while (it.hasNext()) {
HeaderElement elem = it.nextElement();
System.out.println(elem.getName() + " = " + elem.getValue());
NameValuePair[] params = elem.getParameters();
for (int i = 0; i < params.length; i++) {
System.out.println(" " + params[i]);
}
}
1.1.4 HTTP实体
HTTP报文可以携带和请求或响应相关的内容实体。
? streamed流式
? self-contained自我包含式
? wrapping包装式
1.1.4.1. 重复的实体
一个实体可以是可重复的,这意味着它的内容可以被不止一次读取。这是唯一可能与自包含的实体(如ByteArrayEntity或StringEntity)
1.1.4.2. 使用HTTP实体
因为一个实体可以表示二进制和字符的内容,它具有用于字符编码(支持后者,即字符内容)的支持。
该实体被执行与封闭内容或当请求成功并响应体用于将结果发送回客户机的请求时创建。
读取从该实体的内容,人们可以检索经由输入流HttpEntity#的getContent()方法,后者返回一个的java.io.InputStream,或者一个可以提供一个输出流的HttpEntity#的writeTo(OutputStream的)方法中,一旦所有的内容已被写入给定流的返回。
当实体已经接收与传入消息,该方法HttpEntity#的getContentType()和HttpEntity#getContentLength()方法可用于读取通用的元数据,如内容类型和内容长度报头(如果可用)。由于Content-Type头可以包含文本的MIME类型,如文本/纯或text / html的字符编码,该HttpEntity#getContentEncoding()方法来读取这些信息。如果头不可用,长度将返回-1,并为NULL的内容类型。如果Content-Type头可用,一个标题对象将被退回。
当要传出消息创建实体,这个元数据具有由实体的创建者提供。
StringEntity myEntity = new StringEntity("important message",
ContentType.create("text/plain", "UTF-8"));
System.out.println(myEntity.getContentType());
System.out.println(myEntity.getContentLength());
System.out.println(EntityUtils.toString(myEntity));
System.out.println(EntityUtils.toByteArray(myEntity).length);
1.1.5 确保低级别资源释放
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/");
CloseableHttpResponse response = httpclient.execute(httpget);
try {
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
try {
// do something useful
} finally {
instream.close();
}
}
} finally {
response.close();
}
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/");
CloseableHttpResponse response = httpclient.execute(httpget);
try {
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
int byteOne = instream.read();
int byteTwo = instream.read();
// Do not need the rest
}
} finally {
response.close();
}
1.1.6 消耗实体内容
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/");
CloseableHttpResponse response = httpclient.execute(httpget);
try {
HttpEntity entity = response.getEntity();
if (entity != null) {
long len = entity.getContentLength();
if (len != -1 && len < 2048) {
System.out.println(EntityUtils.toString(entity));
} else {
// Stream content out
}
}
} finally {
response.close();
}
1.1.7 生成实体内容
File file = new File("somefile.txt");
FileEntity entity = new FileEntity(file,
ContentType.create("text/plain", "UTF-8"));
HttpPost httppost = new HttpPost("http://localhost/action.do");
httppost.setEntity(entity);
1.1.7.1. HTML表单
许多应用需要模拟提交的HTML形式,例如,为了在登录到Web应用程序或提交的输入数据的处理。HttpClient的提供实体类UrlEncodedFormEntity以促进该过程。
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair("param1", "value1"));
formparams.add(new BasicNameValuePair("param2", "value2"));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, Consts.UTF_8);
HttpPost httppost = new HttpPost("http://localhost/handler.do");
httppost.setEntity(entity);
该UrlEncodedFormEntity实例将使用所谓的URL编码编码参数,并产生以下内容:
param1=value1¶m2=value2
1.1.7.2. 内容分块
一般建议让HttpClient的选择是基于最合适的传输编码被传送的HTTP消息的属性,这是可能的,但是,为了通知HttpClient的那块编码是优选由HttpEntity#setChunked()设置为真。请注意,HttpClient的将使用该标志作为提示而已。使用HTTP协议的版本时,该值将被忽略的不支持块编码,如HTTP/ 1.0。
StringEntity entity = new StringEntity("important message",
ContentType.create("plain/text", Consts.UTF_8));
entity.setChunked(true);
HttpPost httppost = new HttpPost("http://localhost/acrtion.do");
httppost.setEntity(entity);
1.1.8 响应控制器
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/json");
ResponseHandler<MyJsonObject> rh = new ResponseHandler<MyJsonObject>() {
@Override
public JsonObject handleResponse(
final HttpResponse response) throws IOException {
StatusLine statusLine = response.getStatusLine();
HttpEntity entity = response.getEntity();
if (statusLine.getStatusCode() >= 300) {
throw new HttpResponseException(
statusLine.getStatusCode(),
statusLine.getReasonPhrase());
}
if (entity == null) {
throw new ClientProtocolException("Response contains no content");
}
Gson gson = new GsonBuilder().create();
ContentType contentType = ContentType.getOrDefault(entity);
Charset charset = contentType.getCharset();
Reader reader = new InputStreamReader(entity.getContent(), charset);
return gson.fromJson(reader, MyJsonObject.class);
}
};
MyJsonObject myjson = client.execute(httpget, rh);
1.2. HttpClient的接口
HttpClient的接口表示为HTTP请求执行的最本质的合同,它规定了该请求执行处理没有任何限制或特定细节和叶连接管理,状态管理,验证和重定向处理高达个人实现的具体细节。这应该使它更容易装饰与附加功能的接口,如响应内容缓存。
一般HttpClient的实现作为一个门面一些负责处理的HTTP协议如约持续连接永葆持续时间重定向或认证处理或作出决定的某一方面特殊用途的处理程序或策略接口实现。这使得用户选择性地取代的那些方面与定制,应用特定的人默认的实现。
ConnectionKeepAliveStrategy keepAliveStrat = new DefaultConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(
HttpResponse response,
HttpContext context) {
long keepAlive = super.getKeepAliveDuration(response, context);
if (keepAlive == -1) {
// Keep connections alive 5 seconds if a keep-alive value
// has not be explicitly set by the server
keepAlive = 5000;
}
return keepAlive;
}
};
CloseableHttpClient httpclient = HttpClients.custom()
.setKeepAliveStrategy(keepAliveStrat)
.build();
1.2.1. HttpClient的线程安全
HttpClient的实施预计将是线程安全的。建议在这个类的同一个实例是重复使用多次要求执行。
1.2.2. HttpClient的资源释放
当一个实例CloseableHttpClient不再需要和即将走出去的范围与它相关的连接管理器必须通过调用CloseableHttpClient#close()方法关闭。
CloseableHttpClient httpclient = HttpClients.createDefault();
try {
<...>
} finally {
httpclient.close();
}
1.3执行上下文
HttpContext context = <...>
HttpClientContext clientContext = HttpClientContext.adapt(context);
HttpHost target = clientContext.getTargetHost();
HttpRequest request = clientContext.getRequest();
HttpResponse response = clientContext.getResponse();
RequestConfig config = clientContext.getRequestConfig();
CloseableHttpClient httpclient = HttpClients.createDefault();
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(1000)
.setConnectTimeout(1000)
.build();
HttpGet httpget1 = new HttpGet("http://localhost/1");
httpget1.setConfig(requestConfig);
CloseableHttpResponse response1 = httpclient.execute(httpget1, context);
try {
HttpEntity entity1 = response1.getEntity();
} finally {
response1.close();
}
HttpGet httpget2 = new HttpGet("http://localhost/2");
CloseableHttpResponse response2 = httpclient.execute(httpget2, context);
try {
HttpEntity entity2 = response2.getEntity();
} finally {
response2.close();
}
1.4 HTTP协议拦截器
CloseableHttpClient httpclient = HttpClients.custom()
.addInterceptorLast(new HttpRequestInterceptor() {
public void process(
final HttpRequest request,
final HttpContext context) throws HttpException, IOException {
AtomicInteger count = (AtomicInteger) context.getAttribute("count");
request.addHeader("Count", Integer.toString(count.getAndIncrement()));
}
})
.build();
AtomicInteger count = new AtomicInteger(1);
HttpClientContext localContext = HttpClientContext.create();
localContext.setAttribute("count", count);
HttpGet httpget = new HttpGet("http://localhost/");
for (int i = 0; i < 10; i++) {
CloseableHttpResponse response = httpclient.execute(httpget, localContext);
try {
HttpEntity entity = response.getEntity();
} finally {
response.close();
}
}
1.5 异常处理
HttpClient能够抛出两种类型的异常:在I/O失败时,如套接字连接超时或被重置的java.io.IOException异常,还有标志HTTP请求失败的信号,如违反HTTP协议的HttpException异常。
1.5.1 HTTP运输安全
1.5.2 幂等的方法
1.5.3 异常自动恢复
1.5.4 请求重试处理
HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {
public boolean retryRequest(
IOException exception,
int executionCount,
HttpContext context) {
if (executionCount >= 5) {
// Do not retry if over max retry count
return false;
}
if (exception instanceof InterruptedIOException) {
// Timeout
return false;
}
if (exception instanceof UnknownHostException) {
// Unknown host
return false;
}
if (exception instanceof ConnectTimeoutException) {
// Connection refused
return false;
}
if (exception instanceof SSLException) {
// SSL handshake exception
return false;
}
HttpClientContext clientContext = HttpClientContext.adapt(context);
HttpRequest request = clientContext.getRequest();
boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
if (idempotent) {
// Retry if the request is considered idempotent
return true;
}
return false;
}
};
CloseableHttpClient httpclient = HttpClients.custom()
.setRetryHandler(myRetryHandler)
.build();
1.6. 中止请求
在某些情况下,HTTP请求执行失败的预期的时间框架内完成,由于目标服务器或客户端上发出太多的并发请求的高负荷。在这种情况下,可能有必要提前终止该请求并解锁的执行线程通过调用阻塞在一个I / O操作。HTTP请求由HttpClient的可以在执行的任何阶段中止执行HttpUriRequest#中止()方法,该方法是线程安全,并且可以从任何被称为
跟帖当一个HTTP请求被中止它的执行线程 - 即使当前阻塞在I/ O操作 - 这是保证通过投掷InterruptedIOException时解除封锁
1.7 处理重定向
HttpClient的自动处理所有类型的重定向,除了那些明确禁止的HTTP规范需要用户干预。请参阅其他(状态代码303)重定向的POST和PUT请求转换所要求的HTTP规范GET请求。人们可以使用自定义重定向策略,对relaxe由HTTP规范征收POST方法自动重定向限制。
LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();
CloseableHttpClient httpclient = HttpClients.custom()
.setRedirectStrategy(redirectStrategy)
.build();
HttpClient的往往具有重写请求消息在其执行的过程中。每默认HTTP/1.0和HTTP/ 1.1一般使用相对URI请求。同样地,原始请求可能会从位置重定向到另一多次。最后解释绝对HTTP位置可以使用原始请求和上下文来建立。本实用方法URIUtils#决心可以用于构建解释绝对URI用于产生最终的请求。该方法包括从所述重定向的请求或原始请求的最后一个片段标识符。
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpClientContext context = HttpClientContext.create();
HttpGet httpget = new HttpGet("http://localhost:8080/");
CloseableHttpResponse response = httpclient.execute(httpget, context);
try {
HttpHost target = context.getTargetHost();
List<URI> redirectLocations = context.getRedirectLocations();
URI location = URIUtils.resolve(httpget.getURI(), target, redirectLocations);
System.out.println("Final HTTP location: " + location.toASCIIString());
// Expected to be an absolute URI
} finally {
response.close();
}
第二章 连接管理
2.1持久连接
2.2 HTTP连接路由
2.2.1 路由计算
2.2.2安全HTTP连接
2.3 HTTP连接管理器
2.3.1管理连接和连接管理器
HttpClientContext context = HttpClientContext.create();
HttpClientConnectionManager connMrg = new BasicHttpClientConnectionManager();
HttpRoute route = new HttpRoute(new HttpHost("localhost", 80));
// Request new connection. This can be a long process
ConnectionRequest connRequest = connMrg.requestConnection(route, null);
// Wait for connection up to 10 sec
HttpClientConnection conn = connRequest.get(10, TimeUnit.SECONDS);
try {
// If not open
if (!conn.isOpen()) {
// establish connection based on its route info
connMrg.connect(conn, route, 1000, context);
// and mark it as route complete
connMrg.routeComplete(conn, route, context);
}
// Do useful things with the connection.
} finally {
connMrg.releaseConnection(conn, null, 1, TimeUnit.MINUTES);
}
2.3.2简单的连接管理器
2.3.3连接池管理
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// Increase max total connection to 200
cm.setMaxTotal(200);
// Increase default max connection per route to 20
cm.setDefaultMaxPerRoute(20);
// Increase max connections for localhost:80 to 50
HttpHost localhost = new HttpHost("locahost", 80);
cm.setMaxPerRoute(new HttpRoute(localhost), 50);
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(cm)
.build();
2.3.4 连接管理器关闭
CloseableHttpClient httpClient = <...>
httpClient.close();
2. 4 多线程执行请求
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(cm)
.build();
// URIs to perform GETs on
String[] urisToGet = {
"http://www.domain1.com/",
"http://www.domain2.com/",
"http://www.domain3.com/",
"http://www.domain4.com/"
};
// create a thread for each URI
GetThread[] threads = new GetThread[urisToGet.length];
for (int i = 0; i < threads.length; i++) {
HttpGet httpget = new HttpGet(urisToGet[i]);
threads[i] = new GetThread(httpClient, httpget);
}
// start the threads
for (int j = 0; j < threads.length; j++) {
threads[j].start();
}
// join the threads
for (int j = 0; j < threads.length; j++) {
threads[j].join();
}
static class GetThread extends Thread {
private final CloseableHttpClient httpClient;
private final HttpContext context;
private final HttpGet httpget;
public GetThread(CloseableHttpClient httpClient, HttpGet httpget) {
this.httpClient = httpClient;
this.context = HttpClientContext.create();
this.httpget = httpget;
}
@Override
public void run() {
try {
CloseableHttpResponse response = httpClient.execute(
httpget, context);
try {
HttpEntity entity = response.getEntity();
} finally {
response.close();
}
} catch (ClientProtocolException ex) {
// Handle protocol errors
} catch (IOException ex) {
// Handle I/O errors
}
}
}
2.5 连接驱逐策略
public static class IdleConnectionMonitorThread extends Thread {
private final HttpClientConnectionManager connMgr;
private volatile boolean shutdown;
public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) {
super();
this.connMgr = connMgr;
}
@Override
public void run() {
try {
while (!shutdown) {
synchronized (this) {
wait(5000);
// Close expired connections
connMgr.closeExpiredConnections();
// Optionally, close connections
// that have been idle longer than 30 sec
connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
}
}
} catch (InterruptedException ex) {
// terminate
}
}
public void shutdown() {
shutdown = true;
synchronized (this) {
notifyAll();
}
}
}
2.6 连接保持活动的策略
ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
// Honor ‘keep-alive‘ header
HeaderElementIterator it = new BasicHeaderElementIterator(
response.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (it.hasNext()) {
HeaderElement he = it.nextElement();
String param = he.getName();
String value = he.getValue();
if (value != null && param.equalsIgnoreCase("timeout")) {
try {
return Long.parseLong(value) * 1000;
} catch(NumberFormatException ignore) {
}
}
}
HttpHost target = (HttpHost) context.getAttribute(
HttpClientContext.HTTP_TARGET_HOST);
if ("www.naughty-server.com".equalsIgnoreCase(target.getHostName())) {
// Keep alive for 5 seconds only
return 5 * 1000;
} else {
// otherwise keep alive for 30 seconds
return 30 * 1000;
}
}
};
CloseableHttpClient client = HttpClients.custom()
.setKeepAliveStrategy(myStrategy)
.build();
2.7 套接字工厂
HttpClientContext clientContext = HttpClientContext.create();
PlainConnectionSocketFactory sf = PlainConnectionSocketFactory.getSocketFactory();
Socket socket = sf.createSocket(clientContext);
int timeout = 1000; //ms
HttpHost target = new HttpHost("localhost");
InetSocketAddress remoteAddress = new InetSocketAddress(
InetAddress.getByAddress(new byte[] {127,0,0,1}), 80);
sf.connectSocket(timeout, socket, target, remoteAddress, null, clientContext);
2.7.1 安全套接字分层
2.7.2与连接管理器的集成
ConnectionSocketFactory plainsf = <...>
LayeredConnectionSocketFactory sslsf = <...>
Registry<ConnectionSocketFactory> r = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", plainsf)
.register("https", sslsf)
.build();
HttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(r);
HttpClients.custom()
.setConnectionManager(cm)
.build();
2.7.3 SSL/TLS的定制
KeyStore myTrustStore = <...>
SSLContext sslContext = SSLContexts.custom()
.useTLS()
.loadTrustMaterial(myTrustStore)
.build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
2.7.4 主机名验证
SSLContext sslContext = SSLContexts.createSystemDefault();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslContext,
NoopHostnameVerifier.INSTANCE);
PublicSuffixMatcher publicSuffixMatcher = PublicSuffixMatcherLoader.load(
PublicSuffixMatcher.class.getResource("my-copy-effective_tld_names.dat"));
DefaultHostnameVerifier hostnameVerifier = new DefaultHostnameVerifier(publicSuffixMatcher);
2.8 HttpClient代理配置
HttpHost proxy = new HttpHost("someproxy", 8080);
DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
CloseableHttpClient httpclient = HttpClients.custom()
.setRoutePlanner(routePlanner)
.build();
SystemDefaultRoutePlanner routePlanner = new SystemDefaultRoutePlanner(
ProxySelector.getDefault());
CloseableHttpClient httpclient = HttpClients.custom()
.setRoutePlanner(routePlanner)
.build();
HttpRoutePlanner routePlanner = new HttpRoutePlanner() {
public HttpRoute determineRoute(
HttpHost target,
HttpRequest request,
HttpContext context) throws HttpException {
return new HttpRoute(target, null, new HttpHost("someproxy", 8080),
"https".equalsIgnoreCase(target.getSchemeName()));
}
};
CloseableHttpClient httpclient = HttpClients.custom()
.setRoutePlanner(routePlanner)
.build();
}
}
第三章 HTTP状态管理
3.1 HTTP cookies
BasicClientCookie cookie = new BasicClientCookie("name", "value");
// Set effective domain and path attributes
cookie.setDomain(".mycompany.com");
cookie.setPath("/");
// Set attributes exactly as sent by the server
cookie.setAttribute(ClientCookie.PATH_ATTR, "/");
cookie.setAttribute(ClientCookie.DOMAIN_ATTR, ".mycompany.com");
3.2 Cookie规范
该CookieSpec接口表示cookie管理规范。该cookie管理规范有望实施:
?解析的Set-Cookie头的规则。
?解析cookies的验证规则。
?Cookie头的格式对于给定的主机,端口和原籍路径。
HttpClient的附带几个CookieSpec实现:
?Standard strict:国家管理政策符合语法和RFC 6265中定义的wellbehaved轮廓的语义,第4节。
?标准:国家管理政策符合由RFC 6265,用于与现有服务器的互操作性第4节定义更宽松的轮廓不符合很乖轮廓。
?网景草案(过时):该政策符合由Netscape Communications公布了原草案规范。应避免使用,除非绝对必要与旧代码的兼容性。
?RFC 2965(过时):国家管理政策符合由RFC 2965定义的请不要在新的应用程序使用过时的状态管理规范。
?RFC 2109(过时):国家管理政策符合由RFC 2109定义的请不要在新的应用程序使用过时的状态管理规范。
?浏览器兼容性(过时):该政策致力于密切模仿旧版本的浏览器应用程序如Microsoft Internet Explorer和Mozilla Firefox的(MIS)的行为。
请不要在新应用中使用。
?默认:默认的Cookie政策是拿起一个综合的政策无论是RFC 2965,RFC 2109或兼容的实现Netscape的草案基础上,与HTTP响应发送的cookie的属性(如版本的属性,现在已经过时)。这一政策将不再使用,取而代之的标准(RFC 6265标准)实施HttpClient的下一个次要版本的。
?忽略cookies:所有Cookie都将被忽略。
强烈建议使用标准或标准严格的政策,在新的应用程序。
过时的规范应被用于与仅遗留系统的兼容性。支持过时的规范将在HttpClient的下一个主要版本中删除。
3.3 选择cookie策略
RequestConfig globalConfig = RequestConfig.custom()
.setCookieSpec(CookieSpecs.DEFAULT)
.build();
CloseableHttpClient httpclient = HttpClients.custom()
.setDefaultRequestConfig(globalConfig)
.build();
RequestConfig localConfig = RequestConfig.copy(globalConfig)
.setCookieSpec(CookieSpecs.STANDARD_STRICT)
.build();
HttpGet httpGet = new HttpGet("/");
httpGet.setConfig(localConfig);
3.4 定制cookie策略
PublicSuffixMatcher publicSuffixMatcher = PublicSuffixMatcherLoader.getDefault();
Registry<CookieSpecProvider> r = RegistryBuilder.<CookieSpecProvider>create()
.register(CookieSpecs.DEFAULT,
new DefaultCookieSpecProvider(publicSuffixMatcher))
.register(CookieSpecs.STANDARD,
new RFC6265CookieSpecProvider(publicSuffixMatcher))
.register("easy", new EasySpecProvider())
.build();
RequestConfig requestConfig = RequestConfig.custom()
.setCookieSpec("easy")
.build();
CloseableHttpClient httpclient = HttpClients.custom()
.setDefaultCookieSpecRegistry(r)
.setDefaultRequestConfig(requestConfig)
.build();
3.5 Cookie持久化
// Create a local instance of cookie store
CookieStore cookieStore = new BasicCookieStore();
// Populate cookies if needed
BasicClientCookie cookie = new BasicClientCookie("name", "value");
cookie.setDomain(".mycompany.com");
cookie.setPath("/");
cookieStore.addCookie(cookie);
// Set the store
CloseableHttpClient httpclient = HttpClients.custom()
.setDefaultCookieStore(cookieStore)
.build();
3.6 HTTP状态管理和执行上下文
CloseableHttpClient httpclient = <...>
Lookup<CookieSpecProvider> cookieSpecReg = <...>
CookieStore cookieStore = <...>
HttpClientContext context = HttpClientContext.create();
context.setCookieSpecRegistry(cookieSpecReg);
context.setCookieStore(cookieStore);
HttpGet httpget = new HttpGet("http://somehost/");
CloseableHttpResponse response1 = httpclient.execute(httpget, context);
<...>
// Cookie origin details
CookieOrigin cookieOrigin = context.getCookieOrigin();
// Cookie spec used
CookieSpec cookieSpec = context.getCookieSpec();
第四章 HTTP认证
4.1 用户证书
UsernamePasswordCredentials creds = new UsernamePasswordCredentials("user", "pwd");
System.out.println(creds.getUserPrincipal().getName());
System.out.println(creds.getPassword());
NTCredentials creds = new NTCredentials("user", "pwd", "workstation", "domain");
System.out.println(creds.getUserPrincipal().getName());
System.out.println(creds.getPassword());
4.3 凭据提供器
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
new AuthScope("somehost", AuthScope.ANY_PORT),
new UsernamePasswordCredentials("u1", "p1"));
credsProvider.setCredentials(
new AuthScope("somehost", 8080),
new UsernamePasswordCredentials("u2", "p2"));
credsProvider.setCredentials(
new AuthScope("otherhost", 8080, AuthScope.ANY_REALM, "ntlm"),
new UsernamePasswordCredentials("u3", "p3"));
System.out.println(credsProvider.getCredentials(
new AuthScope("somehost", 80, "realm", "basic")));
System.out.println(credsProvider.getCredentials(
new AuthScope("somehost", 8080, "realm", "basic")));
System.out.println(credsProvider.getCredentials(
new AuthScope("otherhost", 8080, "realm", "basic")));
System.out.println(credsProvider.getCredentials(
new AuthScope("otherhost", 8080, null, "ntlm")));
4.4 HTTP认证和执行上下文
CloseableHttpClient httpclient = <...>
CredentialsProvider credsProvider = <...>
Lookup<AuthSchemeProvider> authRegistry = <...>
AuthCache authCache = <...>
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credsProvider);
context.setAuthSchemeRegistry(authRegistry);
context.setAuthCache(authCache);
HttpGet httpget = new HttpGet("http://somehost/");
CloseableHttpResponse response1 = httpclient.execute(httpget, context);
<...>
AuthState proxyAuthState = context.getProxyAuthState();
System.out.println("Proxy auth state: " + proxyAuthState.getState());
System.out.println("Proxy auth scheme: " + proxyAuthState.getAuthScheme());
System.out.println("Proxy auth credentials: " + proxyAuthState.getCredentials());
AuthState targetAuthState = context.getTargetAuthState();
System.out.println("Target auth state: " + targetAuthState.getState());
System.out.println("Target auth scheme: " + targetAuthState.getAuthScheme());
System.out.println("Target auth credentials: " + targetAuthState.getCredentials());
4.5 认证数据的高速缓存
随着4.1版本的HttpClient自动缓存有关主机它已成功地通过身份验证。请注意,必须使用相同的执行上下文来执行逻辑相关的请求,以便缓存认证数据从一个请求到另一个传播。
认证数据将被尽快执行上下文超出范围丢失。
4.6 抢先认证
CloseableHttpClient httpclient = <...>
HttpHost targetHost = new HttpHost("localhost", 80, "http");
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
new AuthScope(targetHost.getHostName(), targetHost.getPort()),
new UsernamePasswordCredentials("username", "password"));
// Create AuthCache instance
AuthCache authCache = new BasicAuthCache();
// Generate BASIC scheme object and add it to the local auth cache
BasicScheme basicAuth = new BasicScheme();
authCache.put(targetHost, basicAuth);
// Add AuthCache to the execution context
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credsProvider);
context.setAuthCache(authCache);
HttpGet httpget = new HttpGet("/");
for (int i = 0; i < 3; i++) {
CloseableHttpResponse response = httpclient.execute(
targetHost, httpget, context);
try {
HttpEntity entity = response.getEntity();
} finally {
response.close();
}
}
4.7 NTLM 认证
4.7.1 NTLM连接持久化
CloseableHttpClient httpclient = <...>
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(AuthScope.ANY,
new NTCredentials("user", "pwd", "myworkstation", "microsoft.com"));
HttpHost target = new HttpHost("www.microsoft.com", 80, "http");
// Make sure the same context is used to execute logically related requests
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credsProvider);
// Execute a cheap method first. This will trigger NTLM authentication
HttpGet httpget = new HttpGet("/ntlm-protected/info");
CloseableHttpResponse response1 = httpclient.execute(target, httpget, context);
try {
HttpEntity entity1 = response1.getEntity();
} finally {
response1.close();
}
// Execute an expensive method next reusing the same context (and connection)
HttpPost httppost = new HttpPost("/ntlm-protected/form");
httppost.setEntity(new StringEntity("lots and lots of data"));
CloseableHttpResponse response2 = httpclient.execute(target, httppost, context);
try {
HttpEntity entity2 = response2.getEntity();
} finally {
response2.close();
}
4.8. SPNEGO / Kerberos身份验证
该SPNEGO(简单和受保护GSSAPI协商机制)被设计为允许身份验证服务时,既不知道到底对方可以使用/提供什么。它是最常用的做Kerberos认证。它可以包装等机制,但目前的版本中HttpClient的设计只使用Kerberos的初衷。
1.客户端的Web浏览器的HTTP GET资源。
2. Web服务器返回的HTTP 401状态和标题:WWW身份验证:协商
3.客户端生成NegTokenInit,BASE64编码它,并重新提交GET与
标题授权:授权:协商<base64编码>。
4.服务器解码NegTokenInit,提取支持MechTypes(仅Kerberos V5在我们的情况下),可确保它是所预计的一个,然后提取MechToken(Kerberos令牌),并验证它。
如果更多的处理是必需的另一个HTTP 401被返回到客户端,在所述WWW-Authenticate头更多的数据。客户需要的信息,并生成另一个令牌传递这回在Authorization头,直到完成。
5.当客户端已经认证了Web服务器应返回的HTTP 200状态,最终WWW-Authenticate头和网页内容。
4.8.1. SPNEGO支持的HttpClient
在SPNEGO认证方案与Sun的Java版本1.5及更高版本兼容。然而,使用Java> = 1.6,强烈推荐,因为它支持更全面SPNEGO认证。
太阳JRE提供了支持类做几乎所有的Kerberos和SPNEGO令牌处理。
这意味着大量的设置是对于GSS类。该SPNegoScheme是一个简单的类来处理编组令牌和读取和写入正确的头。
以最好的方式开始是抢例子KerberosHttpClient.java文件和尝试,并得到它的工作。有很多可能发生的问题,但如果幸运的话它会工作没有太多的问题。
它还应提供一些输出与调试。
在Windows中,应该默认使用登录的凭证;这可以通过使用“的kinit”例如被覆盖$ JAVA_HOME \ BIN \的kinit [email protected],这对于测试和调试问题非常有帮助。通过删除创建的kinit缓存文件恢复到Windows Kerberos缓存。
确保列出domain_realms在krb5.conf文件。这是问题的主要根源。
4.8.2. GSS / Java的Kerberos设置
本文假设您使用的是Windows,但大部分的信息适用于Unix的为好。
该org.ietf.jgss中类有很多可能的配置参数,主要是在krb5.conf / krb5.ini文件。在http://web.mit.edu/kerberos/krb5-1.4/ krb5-1.4.1 / DOC / KRB5管理员/ krb5.conf.html的格式一些更多的信息。
4.8.3. login.conf的文件
下面的配置是一个基本的设置是工作在Windows XP中对IIS和JBoss的谈判模块。
系统属性java.security.auth.login.config可以用来指向login.conf的文件。
login.conf的内容可能看起来像下面这样:
com.sun.security.jgss.login {
com.sun.security.auth.module.Krb5LoginModule所需的客户端= TRUE useTicketCache = TRUE;
};
com.sun.security.jgss.initiate {
com.sun.security.auth.module.Krb5LoginModule所需的客户端= TRUE useTicketCache = TRUE;
};
com.sun.security.jgss.accept {
com.sun.security.auth.module.Krb5LoginModule所需的客户端= TRUE useTicketCache = TRUE;
};
4.8.4. 的krb5.conf / krb5.ini文件
如果不指定,系统默认的将被使用。如果需要通过设置系统属性java.security.krb5.conf指向自定义krb5.conf文件覆盖。
krb5.conf的内容可能看起来像下面这样:
[libdefaults]
default_realm = AD.EXAMPLE.NET
udp_preference_limit = 1
[realms域]
AD.EXAMPLE.NET = {
KDC = KDC.AD.EXAMPLE.NET
}
[domain_realms]
.ad.example.net = AD.EXAMPLE.NET
ad.example.net = AD.EXAMPLE.NET
4.8.5. Windows特定配置
为了让Windows使用当前用户的车票,系统属性javax.security.auth.useSubjectCredsOnly必须设置为false和Windows注册表键allowtgtsessionkey应补充并正确设置允许会话密钥在Kerberos票据授权发送票。
在Windows Server 2003和Windows 2000 SP4,这里是所需的注册表设置:
HKEY_LOCAL_MACHINE \系统\ CurrentControlSet \控制\ LSA \ Kerberos的\参数
值名称:allowtgtsessionkey
数值类型:REG_DWORD
值:0x01
下面是在Windows XP SP2中的注册表设置的位置:
HKEY_LOCAL_MACHINE \系统\ CurrentControlSet \控制\ LSA \ Kerberos的\
值名称:allowtgtsessionkey
数值类型:REG_DWORD
值:0x01
第五章流利的API
5.1. 易于使用的API门面
由于版本4.2的HttpClient配备了一个易于使用的API门面基于一个流畅的界面的概念。流利的门面API暴露的HttpClient只有最基本的功能,适用于简单的用例不需要的HttpClient的充分的灵活性。举例来说,流畅的外观API免除了用户不必处理连接管理和资源释放。
以下是通过慧聪流利的API执行HTTP请求的几个例子
// Execute a GET with timeout settings and return response content as String.
Request.Get("http://somehost/")
.connectTimeout(1000)
.socketTimeout(1000)
.execute().returnContent().asString();
// Execute a POST with the ‘expect-continue‘ handshake, using HTTP/1.1,
// containing a request body as String and return response content as byte array.
Request.Post("http://somehost/do-stuff")
.useExpectContinue()
.version(HttpVersion.HTTP_1_1)
.bodyString("Important stuff", ContentType.DEFAULT_TEXT)
.execute().returnContent().asBytes();
// Execute a POST with a custom header through the proxy containing a request body
// as an HTML form and save the result to the file
Request.Post("http://somehost/some-form")
.addHeader("X-Custom-header", "stuff")
.viaProxy(new HttpHost("myproxy", 8080))
.bodyForm(Form.form().add("username", "vip").add("password", "secret").build())
.execute().saveContent(new File("result.dump"));
人们也可以直接使用执行程序,以便在特定的安全上下文中执行的请求,由此认证细节被缓存并再用于后续请求。
Executor executor = Executor.newInstance()
.auth(new HttpHost("somehost"), "username", "password")
.auth(new HttpHost("myproxy", 8080), "username", "password")
.authPreemptive(new HttpHost("myproxy", 8080));
executor.execute(Request.Get("http://somehost/"))
.returnContent().asString();
executor.execute(Request.Post("http://somehost/do-stuff")
.useExpectContinue()
.bodyString("Important stuff", ContentType.DEFAULT_TEXT))
.returnContent().asString();
5.1.1. 响应处理
流畅的外观一般API减轻了用户不必处理连接管理和资源释放。在大多数情况下,虽然,这是以具有以在内存中缓冲的响应消息的内容的价格。强烈建议使用ResponseHandler所进行的HTTP响应处理,以避免在内存中缓冲的内容。
Document result = Request.Get("http://somehost/content")
.execute().handleResponse(new ResponseHandler<Document>() {
public Document handleResponse(final HttpResponse response) throws IOException {
StatusLine statusLine = response.getStatusLine();
HttpEntity entity = response.getEntity();
if (statusLine.getStatusCode() >= 300) {
throw new HttpResponseException(
statusLine.getStatusCode(),
statusLine.getReasonPhrase());
}
if (entity == null) {
throw new ClientProtocolException("Response contains no content");
}
DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
ContentType contentType = ContentType.getOrDefault(entity);
if (!contentType.equals(ContentType.APPLICATION_XML)) {
throw new ClientProtocolException("Unexpected content type:" +
contentType);
}
String charset = contentType.getCharset();
if (charset == null) {
charset = HTTP.DEFAULT_CONTENT_CHARSET;
}
return docBuilder.parse(entity.getContent(), charset);
} catch (ParserConfigurationException ex) {
throw new IllegalStateException(ex);
} catch (SAXException ex) {
throw new ClientProtocolException("Malformed XML document", ex);
}
}
});
第六章HTTP缓存
6.1. 一般概念
HttpClient的缓存提供了一个HTTP / 1.1兼容的缓存层要与HttpClient的使用 - 在Java相当于一个浏览器的缓存。落实责任遵循设计模式的链,其中的缓存HttpClient的实施可以成为一个简易替换默认非高速缓存HttpClient的执行情况;可以完全从缓存来满足的请求将不会导致实际原点的请求。陈旧的缓存条目都与原点在可能的情况自动进行验证,使用条件GET和了If-Modified-Since和/或如果 - 无 - 匹配请求头。
HTTP / 1.1的缓存一般被设计为语义透明;即,高速缓冲不应该改变的客户端和服务器之间的请求 - 响应交换的含义。因此,它应该是安全的下降缓存的HttpClient到现有标准的客户端 - 服务器关系。虽然缓存模块是从视图的HTTP协议点客户端的一部分,执行目标是与放置在透明缓存代理的要求相兼容。
最后,缓存HttpClient的包括支持由RFC 5861规定的缓存控制扩展(陈旧,如果错误和过时的,同时,重新验证)。
当缓存HttpClient的执行请求时,它会通过以下流程:
1.检查基本符合HTTP 1.1协议的请求,并试图更正请求。
2.冲洗将由这一请求是无效的任何缓存条目。
3.确定如果当前的请求将是从高速缓存servable。如果没有,直接通过请求到原始服务器并返回响应,如果合适将其高速缓存之后。
4.如果是AA缓存servable请求时,它会尝试从缓存中读取它。如果不是在高速缓存中,调用源服务器和缓存响应,如果合适。
5.如果缓存的响应是适合于担任响应,构建一个包含一个ByteArrayEntity和BasicHttpResponse返回它。否则,试图重新验证对源服务器的高速缓存条目。
6.在一个缓存的响应不能被重新验证的情况下,调用源服务器和缓存响应,如果合适的话。
当HttpClient的缓存收到响应,它会通过以下流程:
1.检查为协议一致性响应
2.确定响应是否缓存
3.如果它是可缓存,试图读取到在配置所允许的最大尺寸,并将其存储在缓存中。
4.如果反应过大的缓存,重建部分消耗和响应的情况下直接将其高速缓存返回。
要注意的是缓存HttpClient的不是它本身,一个不同的实施HttpClient的,但它的工作原理是插入本身作为产生额外处理组件对请求执行管线是很重要的。
6.2. RFC-2616符合
我们相信HttpClient的缓存是无条件地符合RFC-2616[RFC http://www.ietf.org// rfc2616.txt]。也就是说,只要规范指示必须不绝,应该或不应该用于HTTP缓存,缓存层试图表现得满足这些要求的方式。这意味着缓存模块不会产生不正确的行为,当你把它研究。
6.3. 用法示例
这是如何建立一个基本的缓存HttpClient的一个简单的例子。如配置,它将存储最多1000缓存对象,其每一个可具有8192字节的最大体尺寸。
此处所选的数字仅仅是作为示例并且不旨在是说明性的或视为建议。
CacheConfig cacheConfig = CacheConfig.custom()
.setMaxCacheEntries(1000)
.setMaxObjectSize(8192)
.build();
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(30000)
.setSocketTimeout(30000)
.build();
CloseableHttpClient cachingClient = CachingHttpClients.custom()
.setCacheConfig(cacheConfig)
.setDefaultRequestConfig(requestConfig)
.build();
HttpCacheContext context = HttpCacheContext.create();
HttpGet httpget = new HttpGet("http://www.mydomain.com/content/");
CloseableHttpResponse response = cachingClient.execute(httpget, context);
try {
CacheResponseStatus responseStatus = context.getCacheResponseStatus();
switch (responseStatus) {
case CACHE_HIT:
System.out.println("A response was generated from the cache with " +
"no requests sent upstream");
break;
case CACHE_MODULE_RESPONSE:
System.out.println("The response was generated directly by the " +
"caching module");
break;
case CACHE_MISS:
System.out.println("The response came from an upstream server");
break;
case VALIDATED:
System.out.println("The response was generated from the cache " +
"after validating the entry with the origin server");
break;
}
} finally {
response.close();
}
6.4. 组态
缓存HttpClient的继承了所有的配置选项,默认的非缓存实现的参数(这包括像设置超时和连接池大小选项)。对于cachingspecific配置,可以提供一个CacheConfig实例跨越以下几个方面定制行为:
缓存大小。如果后端存储支持这些限制,您可以指定缓存条目的最大数量,以及最大缓存响应的车身尺寸。
公共/私有缓存。默认情况下,缓存模块认为自己是一个共享(公共)高速缓存,并且不会,例如,高速缓存的响应与标有“缓存控制:私人”授权头或响应请求。但是,如果缓存只会用在一个逻辑上的“用户”(行为类似于浏览器缓存),那么你将要关闭共享的高速缓存设置。
启发式caching.Per RFC2616,高速缓存可以缓存,即使没有明确的缓存控制报头被设置原点一定的缓存条目。此行为是默认关闭的,但是您可能希望把这个上,如果您正在使用一个原点,不设置适当的标题,但在这里,你仍然想缓存响应。您将要启用启发式缓存,然后指定一个默认的新鲜感寿命和/或时间的一小部分,因为资源的最后修改。为启发式缓存的更多详细信息,请参见13.2.2节和HTTP的13.2.4 / 1.1 RFC。
背景验证。高速缓存模块支持RFC5861的陈旧,同时,重新验证指令,这使得某些缓存条目重新确认发生在后台。您可能需要调整设置为最小和最大数量的背景工作者线程,以及它们可以被回收之前闲置的最长时间。还可以控制用于重新确认队列的大小时,有不足够的工人以跟上需求。
6.5. 存储后端
HttpClient的缓存的缺省实现存储在内存中的应用程序的JVM缓存条目和缓存响应机构。虽然这提供了高性能,它可能不适合你的应用,由于尺寸上的限制,或因为缓存条目是短暂的,没有生存重新启动应用程序。当前版本包括用于存储使用的Ehcache和memcached的实现,它允许溢出缓存项到磁盘或者将它们存储在外部进程缓存条目的支持。
如果没有这些选项是适合你的应用,可以通过实现HttpCacheStorage接口,然后供应,要在施工时间缓存HttpClient的提供自己的存储后端。在这种情况下,高速缓存条目将使用方案来存储,但你会得到重用周围所有的HTTP / 1.1合规性和缓存处理的逻辑。一般来说,它应该有可能创造一个HttpCacheStorage实施选自任何支持一键/值存储(类似于Java Map接口)与应用原子更新的能力。
最后,有一些额外的努力,这是完全有可能建立一个多层次的高速缓存层次结构;例如,包装在内存中缓存的HttpClient围绕一个存储在磁盘上或在远程memcached的缓存项,以下类似虚拟内存,L1 / L2处理器缓存等模式
第七章 高级主题
7.1 自定义客户端连接
? 提供一个自定义LineParser/LineFormatter接口实现
class MyLineParser extends BasicLineParser {
@Override
public Header parseHeader(
CharArrayBuffer buffer) throws ParseException {
try {
return super.parseHeader(buffer);
} catch (ParseException ex) {
// Suppress ParseException exception
return new BasicHeader(buffer.toString(), null);
}
}
}
? 提过一个自定义的OperatedClientConnection实现
HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory =
new ManagedHttpClientConnectionFactory(
new DefaultHttpRequestWriterFactory(),
new DefaultHttpResponseParserFactory(
new MyLineParser(), new DefaultHttpResponseFactory()));
? 为了创建新类的连接,提供一个自定义的ClientConnectionOperator接口实现
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(
connFactory);
CloseableHttpClient httpclient = HttpClients.custom()
.setConnectionManager(cm)
.build();
7.2 有状态的HTTP连接
7.2.1 用户令牌处理器
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpClientContext context = HttpClientContext.create();
HttpGet httpget = new HttpGet("http://localhost:8080/");
CloseableHttpResponse response = httpclient.execute(httpget, context);
try {
Principal principal = context.getUserToken(Principal.class);
System.out.println(principal);
} finally {
response.close();
}
UserTokenHandler userTokenHandler = new UserTokenHandler() {
public Object getUserToken(HttpContext context) {
return context.getAttribute("my-token");
}
};
CloseableHttpClient httpclient = HttpClients.custom()
.setUserTokenHandler(userTokenHandler)
.build();
7.2.2 持续的连接状态
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpClientContext context1 = HttpClientContext.create();
HttpGet httpget1 = new HttpGet("http://localhost:8080/");
CloseableHttpResponse response1 = httpclient.execute(httpget1, context1);
try {
HttpEntity entity1 = response1.getEntity();
} finally {
response1.close();
}
Principal principal = context1.getUserToken(Principal.class);
HttpClientContext context2 = HttpClientContext.create();
context2.setUserToken(principal);
HttpGet httpget2 = new HttpGet("http://localhost:8080/");
CloseableHttpResponse response2 = httpclient.execute(httpget2, context2);
try {
HttpEntity entity2 = response2.getEntity();
} finally {
response2.close();
}
7.3. 使用FutureRequestExecutionService
使用FutureRequestExecutionService,你可以安排HTTP调用和治疗的反应作为未来。这是有用的,当如进行多次调用Web服务。使用FutureRequestExecutionService的好处是,你可以使用多个线程并发调度请求,在任务设置超时或取消时,他们的响应不再是必要的。
FutureRequestExecutionService包装用HttpRequestFutureTask,它扩展FutureTask提供的请求。这个类可以让你取消任务,以及跟踪各种指标,如要求时间。
7.3.1. 创建FutureRequestExecutionService
的构造futureRequestExecutionService采取任何现有的HttpClient的实例和ExecutorService的实例。当配置两个,它对准的最大连接数与您将要使用的线程的数量是非常重要的。当有比连接多个线程,所述连接可以启动超时,因为没有可用的连接。当有比多线程连接,futureRequestExecutionService不会使用所有的人
HttpClient httpClient = HttpClientBuilder.create().setMaxConnPerRoute(5).build();
ExecutorService executorService = Executors.newFixedThreadPool(5);
FutureRequestExecutionService futureRequestExecutionService =
new FutureRequestExecutionService(httpClient, executorService);
7.3.2. 调度请求
要安排一个请求,只是提供一个HttpUriRequest,HttpContext的,和ResponseHandler的。
因为该请求由执行服务处理,一个ResponseHandler的是强制性的。
private final class OkidokiHandler implements ResponseHandler<Boolean> {
public Boolean handleResponse(
final HttpResponse response) throws ClientProtocolException, IOException {
return response.getStatusLine().getStatusCode() == 200;
}
}
HttpRequestFutureTask<Boolean> task = futureRequestExecutionService.execute(
new HttpGet("http://www.google.com"), HttpClientContext.create(),
new OkidokiHandler());
// blocks until the request complete and then returns true if you can connect to Google
boolean ok=task.get();
7.3.3。取消任务
计划任务可能会被取消。如果任务尚未执行,而只是排队等待执行,它只是将永远不执行。如果正在执行和mayInterruptIfRunning参数设置为true,中止()将被调用的请求;否则,响应将被忽略,但该请求将被允许正常完成。任何后续调用task.get()将一个IllegalStateException失败。应当注意到,取消任务仅可以释放客户端的资源。该请求实际上可以在服务器端正常处理。
task.cancel(true)
task.get() // throws an Exception
7.3.4. 回调
不用手动调用task.get(),你也可以使用获得的回调请求完成时FutureCallback实例。这是相同的接口被用于在HttpAsyncClient
private final class MyCallback implements FutureCallback<Boolean> {
public void failed(final Exception ex) {
// do something
}
public void completed(final Boolean result) {
// do something
}
public void cancelled() {
// do something
}
}
HttpRequestFutureTask<Boolean> task = futureRequestExecutionService.execute(
new HttpGet("http://www.google.com"), HttpClientContext.create(),
new OkidokiHandler(), new MyCallback());
7.3.5. 度量
FutureRequestExecutionService通常用于在应用程序,使大量的web服务调用。为了便于例如监测或配置调整,在FutureRequestExecutionService跟踪的几个指标。
每个HttpRequestFutureTask提供的方法来获取任务安排的时间,开始和结束。此外,请求和任务持续时间可用。这些指标汇总到FutureRequestExecutionService在FutureRequestExecutionMetrics实例可以通过FutureRequestExecutionService.metrics访问()。
task.scheduledTime() // returns the timestamp the task was scheduled
task.startedTime() // returns the timestamp when the task was started
task.endedTime() // returns the timestamp when the task was done executing
task.requestDuration // returns the duration of the http request
task.taskDuration // returns the duration of the task from the moment it was scheduled
FutureRequestExecutionMetrics metrics = futureRequestExecutionService.metrics()
metrics.getActiveConnectionCount() // currently active connections
metrics.getScheduledConnectionCount(); // currently scheduled connections
metrics.getSuccessfulConnectionCount(); // total number of successful requests
metrics.getSuccessfulConnectionAverageDuration(); // average request duration
metrics.getFailedConnectionCount(); // total number of failed tasks
metrics.getFailedConnectionAverageDuration(); // average duration of failed tasks
metrics.getTaskCount(); // total number of tasks scheduled
metrics.getRequestCount(); // total number of requests
metrics.getRequestAverageDuration(); // average request duration
metrics.getTaskAverageDuration(); // average task duration
(完)