HttpClient有一个对链接初始化和终止,还有在活动链接上I/O操做的完整控制。而链接操做的不少方面可使用一些参数来控制。html
这些参数能够影响链接操做:java
从一个主机向另一个创建链接的过程是至关复杂的,并且包含了两个终端之间的不少包的交换,它是至关费时的。链接握手的开销是很重要的,特别是对小量的HTTP报文。若是打开的链接能够被重用来执行屡次请求,那么就能够达到很高的数据吞吐量。node
HTTP/1.1强调HTTP链接默认状况能够被重用于屡次请求。HTTP/1.0兼容的终端也可使用类似的机制来明确地交流它们的偏好来保证链接处于活动状态,也使用它来处理多个请求。HTTP代理也能够保持空闲链接处于一段时间的活动状态,防止对相同目标主机的一个链接也许对随后的请求须要。保持链接活动的能力一般被称做持久性链接。HttpClient彻底支持持久性链接。算法
HttpClient可以直接或经过路由创建链接到目标主机,这会涉及多个中间链接,也被称为跳。HttpClient区分路由和普通链接,通道和分层。通道链接到目标主机的多个中间代理的使用也称做是代理链。浏览器
普通路由由链接到目标或仅第一次的代理来建立。通道路由经过代理链到目标链接到第一通道来创建。没有代理的路由不是通道的,分层路由经过已存在链接的分层协议来创建。协议仅仅能够在到目标的通道上或在没有代理的直接链接上分层。缓存
RouteInfo接口表明关于最终涉及一个或多个中间步骤或跳的目标主机路由的信息。HttpRoute是RouteInfo的具体实现,这是不能改变的(是不变的)。HttpTracker是可变的RouteInfo实现,由HttpClient在内部使用来跟踪到最大路由目标的剩余跳数。HttpTracker能够在成功执行向路由目标的下一跳以后更新。HttpRouteDirector是一个帮助类,能够用来计算路由中的下一跳。这个类由HttpClient在内部使用。安全
HttpRoutePlanner是一个表明计算到基于执行上下文到给定目标完整路由策略的接口。HttpClient附带两个默认的HttpRoutePlanner实现。ProxySelectorRoutePlanner是基于java.net.ProxySelector的。默认状况下,它会从系统属性中或从运行应用程序的浏览器中选取JVM的代理设置。DefaultHttpRoutePlanner实现既不使用任何Java系统属性,也不使用系统或浏览器的代理设置。它只基于HTTP以下面描述的参数计算路由。服务器
若是信息在两个不能由非认证的第三方进行读取或修改的终端之间传输,HTTP链接能够被认为是安全的。SSL/TLS协议是用来保证HTTP传输安全使用最普遍的技术。而其它加密技术也能够被使用。一般来讲,HTTP传输是在SSL/TLS加密链接之上分层的。网络
LayeredSocketFactory是SocketFactory接口的扩展。分层的套接字工厂可HTTP链接内部使用java.net.Socket对象来处理数据在线路上的传输。它们依赖SocketFactory接口来建立,初始化和链接套接字。这会使得HttpClient的用户能够提供在运行时指定套接字初始化代码的应用程序。PlainSocketFactory是建立和初始化普通的(不加密的)套接字的默认工厂。多线程
建立套接字的过程和链接到主机的过程是不成对的,因此套接字在链接操做封锁时能够被关闭。
PlainSocketFactory sf = PlainSocketFactory.getSocketFactory();Socket socket = sf.createSocket();HttpParams params = new BasicHttpParams();params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 1000L);sf.connectSocket(socket, "locahost", 8080, null, -1, params);
LayeredSocketFactory是SocketFactory接口的扩展。分层的套接字工厂能够建立在已经存在的普通套接字之上的分层套接字。套接字分层主要经过代理来建立安全的套接字。HttpClient附带实现了SSL/TLS分层的SSLSocketFactory。请注意HttpClient不使用任何自定义加密功能。它彻底依赖于标准的Java密码学(JCE)和安全套接字(JSEE)扩展。
HttpClient使用SSLSocketFactory来建立SSL链接。SSLSocketFactory容许高度定制。它可使用javax.net.ssl.SSLContext的实例做为参数,并使用它来建立定制SSL链接。
TrustManager easyTrustManager = new X509TrustManager() {@Overridepublic void checkClientTrusted(X509Certificate[] chain,String authType) throws CertificateException {// 哦,这很简单!}@Overridepublic void checkServerTrusted(X509Certificate[] chain,String authType) throws CertificateException {//哦,这很简单!}@Overridepublic X509Certificate[] getAcceptedIssuers() {return null;}};SSLContext sslcontext = SSLContext.getInstance("TLS");sslcontext.init(null, new TrustManager[] { easyTrustManager }, null);SSLSocketFactory sf = new SSLSocketFactory(sslcontext);SSLSocket socket = (SSLSocket) sf.createSocket();socket.setEnabledCipherSuites(new String[] { "SSL_RSA_WITH_RC4_128_MD5" });HttpParams params = new BasicHttpParams();params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 1000L);sf.connectSocket(socket, "locahost", 443, null, -1, params);
每个默认的HttpClient使用BrowserCompatHostnameVerifier的实现。若是须要的话,它能够指定不一样的主机名验证器实现。
SSLSocketFactory sf = new SSLSocketFactory(SSLContext.getInstance("TLS"));sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
Scheme类表明了一个协议模式,好比“http”或“https”同时包含一些协议属性,好比默认端口,用来为给定协议建立java.net.Socket实例的套接字工厂。SchemeRegistry类用来维持一组Scheme,当去经过请求URI创建链接时,HttpClient能够从中选择:
Scheme http = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);SSLSocketFactory sf = new SSLSocketFactory(SSLContext.getInstance("TLS"));sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);Scheme https = new Scheme("https", sf, 443);SchemeRegistry sr = new SchemeRegistry();sr.register(http);sr.register(https);
尽管HttpClient了解复杂的路由模式和代理链,它仅支持简单直接的或开箱的跳式代理链接。
告诉HttpClient经过代理去链接到目标主机的最简单方式是经过设置默认的代理参数:
DefaultHttpClient httpclient = new DefaultHttpClient();HttpHost proxy = new HttpHost("someproxy", 8080);httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
也能够构建HttpClient使用标准的JRE代理选择器来得到代理信息:
DefaultHttpClient httpclient = new DefaultHttpClient();ProxySelectorRoutePlanner routePlanner = new ProxySelectorRoutePlanner(httpclient.getConnectionManager().getSchemeRegistry(),ProxySelector.getDefault());httpclient.setRoutePlanner(routePlanner);
另一种选择,能够提供一个定制的RoutePlanner实现来得到HTTP路由计算处理上的复杂的控制:
DefaultHttpClient httpclient = new DefaultHttpClient();httpclient.setRoutePlanner(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()));}});
链接操做是客户端的低层套接字或能够经过外部实体,一般称为链接操做的被操做的状态的链接。OperatedClientConnection接口扩展了HttpClientConnection接口并且定义了额外的控制链接套接字的方法。ClientConnectionOperator接口表明了建立实例和更新那些对象低层套接字的策略。实现类最有可能利用SocketFactory来建立java.net.Socket实例。ClientConnectionOperator接口可让HttpClient的用户提供一个链接操做的定制策略和提供可选实现OperatedClientConnection接口的能力。
HTTP链接是复杂的,有状态的,线程不安全的对象须要正确的管理以便正确地执行功能。HTTP链接在同一时间仅仅只能由一个执行线程来使用。HttpClient采用一个特殊实体来管理访问HTTP链接,这被称为HTTP链接管理器,表明了ClientConnectionManager接口。一个HTTP链接管理器的目的是做为工厂服务于新的HTTP链接,管理持久链接和同步访问持久链接来确保同一时间仅有一个线程能够访问一个链接。
内部的HTTP链接管理器和OperatedClientConnection实例一块儿工做,可是它们为服务消耗器ManagedClientConnection提供实例。ManagedClientConnection扮演链接之上管理状态控制全部I/O操做的OperatedClientConnection实例的包装器。它也抽象套接字操做,提供打开和更新去建立路由套接字便利的方法。ManagedClientConnection实例了解产生它们到链接管理器的连接,并且基于这个事实,当再也不被使用时,它们必须返回到管理器。ManagedClientConnection类也实现了ConnectionReleaseTrigger接口,能够被用来触发释放链接返回给管理器。一旦释放链接操做被触发了,被包装的链接从ManagedClientConnection包装器中脱离,OperatedClientConnection实例被返回给管理器。尽管服务消耗器仍然持有ManagedClientConnection实例的引用,它也再也不去执行任何I/O操做或有意无心地改变的OperatedClientConnection状态。
这里有一个从链接管理器中获取链接的示例:
HttpParams params = new BasicHttpParams();Scheme http = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);SchemeRegistry sr = new SchemeRegistry();sr.register(http);ClientConnectionManager connMrg = new SingleClientConnManager(params, sr);// 请求新链接。这多是一个很长的过程。ClientConnectionRequest connRequest = connMrg.requestConnection(new HttpRoute(new HttpHost("localhost", 80)), null);// 等待链接10秒ManagedClientConnection conn = connRequest.getConnection(10, TimeUnit.SECONDS);try {// 用链接在作有用的事情。当完成时释放链接。conn.releaseConnection();} catch (IOException ex) {// 在I/O error之上终止链接。conn.abortConnection();throw ex;}
若是须要,链接请求能够经过调用来ClientConnectionRequest#abortRequest()方法过早地中断。这会解锁在ClientConnectionRequest#getConnection()方法中被阻止的线程。
一旦响应内容被彻底消耗后,BasicManagedEntity包装器类能够用来保证自动释放低层的链接。HttpClient内部使用这个机制来实现透明地对全部从HttpClient#execute()方法中得到响应释放链接:
ClientConnectionRequest connRequest = connMrg.requestConnection(new HttpRoute(new HttpHost("localhost", 80)), null);ManagedClientConnection conn = connRequest.getConnection(10, TimeUnit.SECONDS);try {BasicHttpRequest request = new BasicHttpRequest("GET", "/");conn.sendRequestHeader(request);HttpResponse response = conn.receiveResponseHeader();conn.receiveResponseEntity(response);HttpEntity entity = response.getEntity();if (entity != null) {BasicManagedEntity managedEntity = new BasicManagedEntity(entity, conn, true);// 替换实体response.setEntity(managedEntity);}// 使用响应对象作有用的事情。当响应内容被消耗后这个链接将会自动释放。} catch (IOException ex) {//在I/O error之上终止链接。conn.abortConnection();throw ex;}
SingleClientConnManager是一个简单的链接管理器,在同一时间它仅仅维护一个链接。尽管这个类是线程安全的,但它应该被用于一个执行线程。SingleClientConnManager对于同一路由的后续请求会尽可能重用链接。而若是持久链接的路由不匹配链接请求的话,它也会关闭存在的链接以后对给定路由再打开一个新的。若是链接已经被分配,将会抛出java.lang.IllegalStateException异常。
对于每一个默认链接,HttpClient使用SingleClientConnManager。
ThreadSafeClientConnManager是一个复杂的实现来管理客户端链接池,它也能够从多个执行线程中服务链接请求。对每一个基本的路由,链接都是池管理的。对于路由的请求,管理器在池中有可用的持久性链接,将被从池中租赁链接服务,而不是建立一个新的链接。
ThreadSafeClientConnManager维护每一个基本路由的最大链接限制。每一个默认的实现对每一个给定路由将会建立不超过两个的并发链接,而总共也不会超过20个链接。对于不少真实的应用程序,这个限制也证实很大的制约,特别是他们在服务中使用HTTP做为传输协议。链接限制,也可使用HTTP参数来进行调整。
这个示例展现了链接池参数是如何来调整的:
HttpParams params = new BasicHttpParams();// 增长最大链接到200ConnManagerParams.setMaxTotalConnections(params, 200);// 增长每一个路由的默认最大链接到20ConnPerRouteBean connPerRoute = new ConnPerRouteBean(20);// 对localhost:80增长最大链接到50HttpHost localhost = new HttpHost("locahost", 80);connPerRoute.setMaxForRoute(new HttpRoute(localhost), 50);ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);SchemeRegistry schemeRegistry = new SchemeRegistry();schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);HttpClient httpClient = new DefaultHttpClient(cm, params);
当一个HttpClient实例再也不须要时,并且即将走出使用范围,那么关闭链接管理器来保证由管理器保持活动的全部链接被关闭,由链接分配的系统资源被释放是很重要的。
DefaultHttpClient httpclient = new DefaultHttpClient();HttpGet httpget = new HttpGet("http://www.google.com/");HttpResponse response = httpclient.execute(httpget);HttpEntity entity = response.getEntity();System.out.println(response.getStatusLine());if (entity != null) {entity.consumeContent();}httpclient.getConnectionManager().shutdown();
当配备链接池管理器时,好比ThreadSafeClientConnManager,HttpClient能够同时被用来执行多个请求,使用多线程执行。
ThreadSafeClientConnManager将会分配基于它的配置的链接。若是对于给定路由的全部链接都被租出了,那么链接的请求将会阻塞,直到一个链接被释放回链接池。它能够经过设置'http.conn-manager.timeout'为一个正数来保证链接管理器不会在链接请求执行时无限期的被阻塞。若是链接请求不能在给定的时间周期内被响应,将会抛出ConnectionPoolTimeoutException异常。
HttpParams params = new BasicHttpParams();SchemeRegistry schemeRegistry = new SchemeRegistry();schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);HttpClient httpClient = new DefaultHttpClient(cm, params);// 执行GET方法的URIString[] 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();}// 合并线程for (int j = 0; j < threads.length; j++) {threads[j].join();}static class GetThread extends Thread {private final HttpClient httpClient;private final HttpContext context;private final HttpGet httpget;public GetThread(HttpClient httpClient, HttpGet httpget) {this.httpClient = httpClient;this.context = new BasicHttpContext();this.httpget = httpget;}@Overridepublic void run() {try {HttpResponse response = this.httpClient.execute(this.httpget, this.context);HttpEntity entity = response.getEntity();if (entity != null) {// 对实体作些有用的事情...// 保证链接能释放回管理器entity.consumeContent();}} catch (Exception ex) {this.httpget.abort();}}}
一个经典的阻塞I/O模型的主要缺点是网络套接字仅当I/O操做阻塞时才能够响应I/O事件。当一个链接被释放返回管理器时,它能够被保持活动状态而却不能监控套接字的状态和响应任何I/O事件。若是链接在服务器端关闭,那么客户端链接也不能去侦测链接状态中的变化和关闭本端的套接字去做出适当响应。
HttpClient经过测试链接是不是过期的来尝试去减轻这个问题,这已经再也不有效了,由于它已经在服务器端关闭了,以前使用执行HTTP请求的链接。过期的链接检查也并非100%的稳定,反而对每次请求执行还要增长10到30毫秒的开销。惟一可行的而不涉及到每一个对空闲链接的套接字模型线程解决方案,是使用专用的监控线程来收回由于长时间不活动而被认为是过时的链接。监控线程能够周期地调用ClientConnectionManager#closeExpiredConnections()方法来关闭全部过时的链接,从链接池中收回关闭的链接。它也能够选择性调用ClientConnectionManager#closeIdleConnections()方法来关闭全部已经空闲超过给定时间周期的链接。
public static class IdleConnectionMonitorThread extends Thread {private final ClientConnectionManager connMgr;private volatile boolean shutdown;public IdleConnectionMonitorThread(ClientConnectionManager connMgr) {super();this.connMgr = connMgr;}@Overridepublic void run() {try {while (!shutdown) {synchronized (this) {wait(5000);// 关闭过时链接connMgr.closeExpiredConnections();// 可选地,关闭空闲超过30秒的链接connMgr.closeIdleConnections(30, TimeUnit.SECONDS);}}} catch (InterruptedException ex) {// 终止}}public void shutdown() {shutdown = true;synchronized (this) {notifyAll();}}}
HTTP规范没有肯定一个持久链接可能或应该保持活动多长时间。一些HTTP服务器使用非标准的头部信息Keep-Alive来告诉客户端它们想在服务器端保持链接活动的周期秒数。若是这个信息可用,HttClient就会利用这个它。若是头部信息Keep-Alive在响应中不存在,HttpClient假设链接无限期的保持活动。然而许多现实中的HTTP服务器配置了在特定不活动周期以后丢掉持久链接来保存系统资源,每每这是不通知客户端的。若是默认的策略证实是过于乐观的,那么就会有人想提供一个定制的保持活动策略。