2.1 连接持久性

建立从一个主机到另一个主机的连接的过程相当复杂,并且涉及两个端点之间的多个分组交换,这可能相当耗时。连接握手的开销可能很大,特别是对于小型的HTTP消息。 如果可以重新使用开放连接来执行多个请求,则可以实现更高的数据吞吐量。

HTTP / 1.1规定HTTP连接可以重复用于多个请求。 符合HTTP / 1.0的端点还可以使用一种机制来显式传达它们的首选项,以保持连接的活动状态并将其用于多个请求。 HTTP代理还可以保持空闲连接在一段时间内保持活动状态,以防后续请求需要连接到同一个目标主机。 保持连接的能力通常被称为连接持久性。 HttpClient完全支持连接持久性。

2.2 HTTP连接路由

HttpClient能够直接或通过可能涉及多个中间连接(也称为中继)的路由建立到目标主机的连接。 HttpClient将路由的连接区分为普通,隧道和分层。 使用多个中间代理来隧道连接到目标主机被称为代理链接。

隧道路由是通过连接到第一个隧道并通过代理链隧道进行目标建立的。不使用代理服务器的路由不能使用隧道路由。通过在现有连接上分层协议来建立分层路由。协议只能通过隧道到目标,或通过无代理的直接连接。

2.2.1. 路由计算

RouteInfo接口表示关于到涉及一个或多个中间步骤或跳跃的目标主机的确定路由的信息。 HttpRoute是RouteInfo的具体实现,它不能被改变(是不可变的)。 HttpTracker是HttpClient内部使用的一个可变的RouteInfo实现,用于跟踪剩余的跳转到最终的路由目标。HttpTracker可以在向路由目标成功执行下一跳之后更新。 HttpRouteDirector是一个辅助类,可以用来计算路由中的下一步。这个类由HttpClient在内部使用。

HttpRoutePlanner是一个接口,它代表一个基于执行上下文来计算给定目标的完整路由的策略。 HttpClient附带两个默认的HttpRoutePlanner实现。 SystemDefaultRoutePlanner基于java.net.ProxySelector。默认情况下,它将从系统属性或运行应用程序的浏览器中获取JVM的代理设置。 DefaultProxyRoutePlanner实现不使用任何Java系统属性,也不使用任何系统或浏览器代理设置。它总是通过相同的默认代理来计算路由。

2.2.2. 安全的HTTP连接

如果在两个连接端点之间传输的信息不能被未经授权的第三方读取或篡改,那么HTTP连接可以被认为是安全的。SSL/TLS协议是确保HTTP传输安全性的最广泛使用的技术。 但是,也可以使用其他加密技术。通常,HTTP传输是通过SSL/TLS加密连接分层的。

2.3.HTTP连接管理器

2.3.1. 管理连接和连接管理器

HTTP连接是复杂的,有状态的,线程不安全的对象,需要妥善管理才能正常工作。 HTTP连接一次只能由一个执行线程使用。 HttpClient使用一个特殊的实体来管理对HTTP连接的访问,这个HTTP连接被称为HTTP连接管理器,并由HttpClientConnectionManager接口表示。HTTP连接管理器的目的是作为新的HTTP连接的工厂,管理持久连接的生命周期,并同步对持久连接的访问,以确保一次只有一个线程可以访问连接。内部HTTP连接管理器与ManagedHttpClientConnection实例一起工作,作为管理连接状态和控制I/O操作执行的真实连接的代理。如果托管连接被释放或被其消费者明确关闭,则底层连接从其代理分离,并返回给管理器。即使服务消费者仍然持有对代理实例的引用,它不再有意或无意地执行任何I/O操作或改变真实连接的状态。

    //创建HTTP上下文
HttpClientContext context=HttpClientContext.create();
//创建HTTP连接管理器
HttpClientConnectionManager connMrg=new BasicHttpClientConnectionManager();
//创建连接路由线路
HttpRoute route=new HttpRoute(new HttpHost("www.baidu.com",0, "http://"));
//请求一个新的Connection,这可能需要处理很长时间。
ConnectionRequest connRequest=connMrg.requestConnection(route, null);
//只等待10秒,有可能抛出InterruptedException, ExecutionException 异常
HttpClientConnection conn=connRequest.get(10, TimeUnit.SECONDS);
try {
if(conn.isOpen()) {
//根据Route info建立连接
connMrg.connect(conn, route, 1000, context);
//将其标记为路由已完成
connMrg.routeComplete(conn, route, context);
}
Do useful things with the connection.
}finally {
connMrg.releaseConnection(conn, null, 1, TimeUnit.MINUTES);
}

如有必要,可以通过调用ConnectionRequest#cancel()来过早终止连接请求。 这将解除在ConnectionRequest#get()方法中阻塞的线程。

2.3.2. 简单连接管理器

BasicHttpClientConnectionManager是一个简单的连接管理器,一次只维护一个连接。 即使这个类是线程安全的,它也只能被一个执行线程使用。BasicHttpClientConnectionManager将努力重复使用相同路由的后续请求的连接。但是,如果持续连接的路由与连接请求的路由不匹配,它将关闭现有连接并重新打开给定路由。 如果连接已被分配,则引发java.lang.IllegalStateException。

这个连接管理器的实现应该在EJB容器中使用。

2.3.3. 汇集连接管理器(Pooling connection manager)

PoolingHttpClientConnectionManager是一个更复杂的实现,它管理一个客户端连接池,并能够处理来自多个执行线程的连接请求。连接按照每个路线进行汇集。 对于管理器已经在池中具有持续连接的路由的请求将通过从池租用连接而不是创建全新的连接来服务。

PoolingHttpClientConnectionManager保持每个路由和总共连接的最大限制。 默认情况下,这个实现每个给定的路由创建不超过2个并发连接,总共不超过20个连接。 对于许多真实世界的应用程序来说,这些限制可能会被证明过于严格,特别是如果他们使用HTTP作为其服务的传输协议。

此示例显示连接池参数如何调整:

    PoolingHttpClientConnectionManager cm=new PoolingHttpClientConnectionManager();
//设置最大连接数不超过200
cm.setMaxTotal(200);
//每个路由默认的连接数20
cm.setDefaultMaxPerRoute(20);
HttpHost locaHost=new HttpHost("localhost",80, "http://");
HttpRoute route=new HttpRoute(locaHost);
//路由最大连接数不超过50
cm.setMaxPerRoute(route, 50);
CloseableHttpClient httpclient=HttpClients.custom().setConnectionManager(cm).build();

2.3.4. 关闭连接管理器

当一个HttpClient实例不再需要并且即将离开作用域时,关闭其连接管理器以确保管理器保持活动的所有连接都关闭,并释放由这些连接分配的系统资源是非常重要的。

CloseableHttpClient httpClient = <...>
httpClient.close();

2.4. 多线程执行请求(Multithreaded request execution)

当配备PoolingClientConnectionManager等连接池管理器时,可以使用HttpClient同时使用多个执行线程执行多个请求。PoolingClientConnectionManager将根其配置分配连接。 如果给定路由的所有连接已经租用,连接请求将被阻塞,直到连接释放回池。 可以通过将 http.conn-manager.timeout 设置为正值来确保连接管理器不会在连接请求操作中无限期地阻塞。 如果连接请求在给定的时间内无法被服务,则抛出ConnectionPoolTimeoutException。

//创建PoolingHttpClientConnectionManager连接池管理器
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
//创建HttpClient实例
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(cm)
.build(); // 执行GET请求的URI
String[] urisToGet = {
"http://www.domain1.com/",
"http://www.domain2.com/",
"http://www.domain3.com/",
"http://www.domain4.com/"
}; // 为每个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);
} // 开始线程
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();
}

尽管HttpClient实例是线程安全的并且可以在多个执行线程之间共享,但强烈建议每个线程都维护自己的专用HttpContext实例。

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. 连接驱逐策略(Connection eviction policy)

经典阻塞I/O模型的主要缺点之一是网络套接字只有在I/O操作中被阻塞时才能对I/O事件做出反应。当一个连接释放回管理器时,它可以保持活动状态,但是它无法监视套接字的状态并对任何I/O事件做出反应。如果连接在服务器端被关闭,客户端连接将无法检测到连接状态的变化(并通过关闭套接字来适当地作出反应)。

HttpClient试图通过测试连接是否是“陈旧的”也就是不再有效的,因为它是在服务器端关闭的,在使用连接执行HTTP请求之前,以缓解这个问题。但是陈旧的连接检查不是100%可靠的。对于空闲连接,如果每个套接字模型不涉及一个线程的话,唯一可行解决方案是专用监视器线程,用于驱除由于长时间不活动而被认为过期的连接。监视线程可以定期调用ClientConnectionManager#closeExpiredConnections()方法关闭所有过期的连接,并从池中驱逐关闭的连接。它还可以选择调用ClientConnectionManager#closeIdleConnections()方法来关闭在给定时间段内闲置的所有连接。

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. 保持连接存在的策略(Connection keep alive strategy)

HTTP规范没有指定持续连接可能会保持多久,应该保持活动状态。一些HTTP服务器使用一个非标准的Keep-Alive标头来向客户端传达他们希望在服务器端保持连接的时间段(以秒为单位)。如果可用的话,HttpClient使用这个信息。如果响应中不存在Keep-Alive头,则HttpClient假定连接可以无限期地保持活动状态。 但是,通常使用的许多HTTP服务器被配置为在一段时间不活动之后丢弃持久连接,以节省系统资源,而通常不通知客户端。如果默认策略过于乐观,则可能需要提供自定义保活策略。

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. 连接套接字工厂(Connection socket factories)

HTTP连接在内部使用java.net.Socket对象来处理通过线路传输的数据。 但是他们依靠ConnectionSocketFactory接口来创建,初始化和连接套接字。 这使得HttpClient的用户可以在运行时提供特定于应用程序的套接字初始化代码。 PlainConnectionSocketFactory是创建和初始化普通(未加密)套接字的默认工厂。

创建套接字并将其连接到主机的过程是分离的,以便在连接操作中阻塞套接字。

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. SSL(Secure Sockets Layer 安全套接层)

SSL百度百科

LayeredConnectionSocketFactory是ConnectionSocketFactory接口的扩展。 分层的套接字工厂能够在现有的普通套接字上创建套接字。 套接字分层主要用于通过代理创建安全套接字。 HttpClient附带实现SSL / TLS分层的SSLSocketFactory。 请注意HttpClient不使用任何自定义加密功能。 它完全依赖于标准的Java加密(JCE)和安全套接字(JSEE)扩展。

2.7.2. 集成到连接管理器中(Integration with connection manager)

自定义连接套接字工厂可以与特定协议方案(如HTTP或HTTPS)相关联,然后用于创建自定义连接管理器。

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定制(SSL/TLS customization)

HttpClient使用SSLConnectionSocketFactory创建SSL连接。SSLConnectionSocketFactory允许高度的自定义。它可以将javax.net.ssl.SSLContext的实例作为参数,并使用它创建自定义配置的SSL连接。

KeyStore myTrustStore = <...>
SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial(myTrustStore)
.build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);

SSLConnectionSocketFactory的定制意味着对SSL / TLS协议的概念有一定程度的熟悉,详细的解释不在本文的范围之内。 有关javax.net.ssl.SSLContext和相关工具的详细说明,请参阅Java™安全套接字扩展(JSSE)参考指南

2.7.4. 主机名验证(Hostname verification)

除了在SSL / TLS协议级别上执行信任验证和客户端身份验证之外,HttpClient还可以选择验证目标主机名是否与存储在服务器X.509证书内的名称匹配。 该验证可以提供对服务器信任材料的真实性的附加保证。javax.net.ssl.HostnameVerifier接口表示主机名验证策略。HttpClient提供了两个javax.net.ssl.HostnameVerifier实现。 重要提示:主机名验证不应与SSL信任验证混淆。

- DefaultHostnameVerifier:HttpClient使用的默认实现符合RFC 2818的要求。主机名必须与证书指定的其他名称匹配,或者在没有其他名称给定证书主体的最具体CN的情况下。 通配符可以出现在CN和任何主题中。

- NoopHostnameVerifier:这个主机名验证者本质上关闭主机名验证。 它接受任何有效的SSL会话并匹配目标主机。

默认情况下,HttpClient使用DefaultHostnameVerifier实现。如果需要,可以指定一个不同的主机名验证器实现

SSLContext sslContext = SSLContexts.createSystemDefault();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslContext,
NoopHostnameVerifier.INSTANCE);

从版本4.4开始,HttpClient使用由Mozilla基金会友好维护的公共后缀列表,以确保SSL证书中的通配符不会被滥用以应用于具有公共顶级域的多个域。 HttpClient附带在发布时检索的列表的副本。列表的最新版本可以在https://publicsuffix.org/list/找到。建议清单的本地副本,并从其原始位置每天下载一次,这是非常值得建议的。

PublicSuffixMatcher publicSuffixMatcher = PublicSuffixMatcherLoader.load(
PublicSuffixMatcher.class.getResource("my-copy-effective_tld_names.dat"));
DefaultHostnameVerifier hostnameVerifier = new DefaultHostnameVerifier(publicSuffixMatcher);

通过使用空匹配器,可以禁止对公众足迹进行验证。

DefaultHostnameVerifier hostnameVerifier = new DefaultHostnameVerifier(null);

2.8. HttpClient代理配置

即使HttpClient知道复杂的路由方案和代理链,它只支持简单的直接或一跳代理连接。

告诉HttpClient通过代理连接到目标主机的最简单的方法是设置默认的代理参数:

HttpHost proxy = new HttpHost("someproxy", 8080);
DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
CloseableHttpClient httpclient = HttpClients.custom()
.setRoutePlanner(routePlanner)
.build();

也可以指示HttpClient使用标准JRE代理选择器来获取代理信息:

SystemDefaultRoutePlanner routePlanner = new SystemDefaultRoutePlanner(
ProxySelector.getDefault());
CloseableHttpClient httpclient = HttpClients.custom()
.setRoutePlanner(routePlanner)
.build();

或者,可以提供自定义的RoutePlanner实现,以完全控制HTTP路由计算过程:

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();
}
}

Part2-HttpClient官方教程-Chapter2-连接管理的更多相关文章

  1. Part2-HttpClient官方教程-Chapter3-HTTP状态管理

    ps:近日忙于课设与一个赛事的准备....时间真紧啊~~ 最初,HTTP被设计为一种无状态的,面向请求/响应的协议,它并没有为跨越多个逻辑相关的请求/响应交换的有状态会话做出特殊规定.随着HTTP协议 ...

  2. Part2-HttpClient官方教程-Chapter4-HTTP 认证

    原文链接地址 HttpClient 提供对由 HTTP 标准规范定义的认证模式的完全支持.HttpClient 的认证框架可以扩展支持非标准的认证模式,比如 NTLM 和 SPNEGO. 4.1 用户 ...

  3. httpclient 连接管理器

    连接操作器 连接操作是客户端的底层套接字或可以通过外部实体,通常称为连接操作的被操作的状态的连接. OperatedClientConnection接口扩展了HttpClientConnection接 ...

  4. HttpClient4.3教程 第二章 连接管理

    2.1.持久连接 两个主机建立连接的过程是很复杂的一个过程,涉及到多个数据包的交换,并且也很耗时间.Http连接需要的三次握手开销很大,这一开销对于比较小的http消息来说更大.但是如果我们直接使用已 ...

  5. HttpClient学习研究---第二章:连接管理

    第二章.Connection management连接管理2.1. 2.1.Connection persistence连接持久性The process of establishing a conne ...

  6. Unity性能优化(3)-官方教程Optimizing garbage collection in Unity games翻译

    本文是Unity官方教程,性能优化系列的第三篇<Optimizing garbage collection in Unity games>的翻译. 相关文章: Unity性能优化(1)-官 ...

  7. DroidParts 中文系列教程(基于官方教程)

    DroidParts中文系列教程(基于官方教程) (一)DroidParts框架概况 2014年4月18日星期五 11:36 他是一个精心构造的安卓框架,包括下面这些基本功能 DI依赖注入,可以注入V ...

  8. HttpClient 入门教程学习

    HttpClient简介 HttpClient是基于HttpCore的HTTP/1.1兼容的HTTP代理实现. 它还为客户端认证,HTTP状态管理和HTTP连接管理提供可重用组件. HttpCompo ...

  9. Hololens官方教程精简版 - 08. Sharing holograms(共享全息影像)

    前言 注意:本文已更新到5.5.1f1版本号 本篇集中学习全息影像"共享"的功能,以实如今同一房间的人,看到"同一个物体".之所以打引號,是由于.每一个人看到的 ...

随机推荐

  1. 解决连接mysql报错1130

    最近在服务器上部署好的应用突然间连接不上mysql数据库,报错“ERROR 1130: Host xxx.xxx.xxx.xxx is not allowed to connect to this M ...

  2. 将sublime添加到右键菜单

    sublime text 添加到鼠标右键功能: 把以下内容复制并保存到文件,重命名为:sublime_addright.reg,然后双击就可以了. (注意:需要把下面代码中的Sublime的安装目录( ...

  3. ismember matlab

    ismember 判断A中的元素在B中有没有出现 LIA = ismember(A,B) for arrays A and B returns an array of the same size as ...

  4. 用select (多路复用)模拟一个 socket server

    需求:用select (多路复用)模拟一个 socket server.可以接收多并发. 1. 一开始是检测自己,如果我有活动了,就说明有客户端要连我了. #用select去模拟socket,实现单线 ...

  5. [bzoj4398] 福慧双修 最短路 二进制分组

    ---题面--- 题解: 考场上看的这道题,,,当时70分算法打挂了,今天才知道这个也是原题.... 首先,对于不跟1相邻的边,肯定不会经过两次,因为经过两次就回来了,除了增加路径长度之外没有任何意义 ...

  6. HDU5115:Dire Wolf——题解+翻译

    http://acm.hdu.edu.cn/showproblem.php?pid=5115 题目大意:给n匹狼,每一次攻击可以秒杀一匹狼,但同时会受到这匹狼的a攻击和它相邻两只狼的b攻击. 给定a, ...

  7. fhq_treap 学习笔记

    前言:昨天写NOIp2017队列,写+调辗转了3h+,不知道怎么的,就点进了一个神仙的链接,便在今日学习了神仙的fhq_treap. 简介:fhq_treap功能强大,支持splay支持的所有操作,代 ...

  8. XXE漏洞攻击与防御整理

    一.漏洞原理 1.DTD 文档类型定义(DTD)可定义合法的XML文档构建模块.它使用一系列合法的元素来定义文档的结构.DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用. 内部的 DOC ...

  9. 负载均衡配置(基于Nginx)

    以下是基于nginx进行负载均衡配置的流程: 服务器配置如下: 1.  安装nginx的服务器:192.168.1.1 2.  nginx配置负载均衡位置及端口:192.168.1.1 80端口 3. ...

  10. STL之三:deque用法详解

    转载于:http://blog.csdn.net/longshengguoji/article/details/8519812 deque函数: deque容器为一个给定类型的元素进行线性处理,像向量 ...