关于HttpClient的一些知识(Part Two)
接上文 关于HttpClient的一些知识(Part One) , HttpClient有一个对连接初始化和终止,还有在活动连接上I/O操作的完整控制。而连接操作的很多方面可以使用一些参数来控制。
连接参数
- ‘http.socket.timeout’: 定义了套接字的毫秒级超时时间(SO_TIMEOUT),换句话说,在两个连续的数据包之间最大的闲置时间。如果超时时间是0就解释为是一个无限大的超时时间。这个参数期望得到一个java.lang.Integer类型的值。如果这个参数没有被设置,那么读取操作就不会超时(无限大的超 时时间)。
- ‘http.tcp.nodelay’: 决定了是否使用Nagle算法。Nagle算法视图通过最小化发送的分组数量来节省带宽。当应用程序希望降低网络延迟并提高性能时,它们可以关闭 Nagle算法(也就是开启TCP_NODELAY)。数据将会更早发送,增加了带宽消耗的成文。这个参数期望得到一个 java.lang.Boolean类型的值。如果这个参数没有被设置,那么TCP_NODELAY就会开启(无延迟)。
- ‘http.socket.buffer- size’:决定了内部套接字缓冲使用的大小,来缓冲数据同时接收/传输HTTP报文。这个参数期望得到一个java.lang.Integer类型的 值。如果这个参数没有被设置,那么HttpClient将会分配8192字节的套接字缓存。
- ‘http.socket.linger’: 使用指定的秒数拖延时间来设置SO_LINGER。最大的连接超时值是平台指定的。值0暗示了这个选项是关闭的。值-1暗示了使用了JRE默认的。这个设 置仅仅影响套接字关闭操作。如果这个参数没有被设置,那么就假设值为-1(JRE默认)。
- ‘http.connection.timeout’: 决定了直到连接建立时的毫秒级超时时间。超时时间的值为0解释为一个无限大的时间。这个参数期望得到一个java.lang.Integer类型的值。如 果这个参数没有被设置,连接操作将不会超时(无限大的超时时间)。
- ‘http.connection.stalecheck’: 决定了是否使用旧的连接检查。当在一个连接之上执行一个请求而服务器端的连接已经关闭时,关闭旧的连接检查可能导致在获得一个I/O错误风险时显著的性能 提升(对于每一个请求,检查时间可以达到30毫秒)。这个参数期望得到一个java.lang.Boolean类型的值。出于性能的关键操作,检查应该被 关闭。如果这个参数没有被设置,那么旧的连接将会在每个请求执行之前执行。
- ‘http.connection.max- line-length’:决定了最大请求行长度的限制。如果设置为一个正数,任何HTTP请求行超过这个限制将会引发 java.io.IOException异常。负数或零将会关闭这个检查。这个参数期望得到一个java.lang.Integer类型的值。如果这个参 数没有被设置,那么就不强制进行限制了。
- ‘http.connection.max- header-count’:决定了允许的最大HTTP头部信息数量。如果设置为一个正数,从数据流中获得的HTTP头部信息数量超过这个限制就会引发 java.io.IOException异常。负数或零将会关闭这个检查。这个参数期望得到一个java.lang.Integer类型的值。如果这个参 数没有被设置,那么就不 强制进行限制了。
- ‘http.connection.max- status-line-garbage’:决定了在期望得到HTTP响应状态行之前可忽略请求行的最大数量。使用HTTP/1.1持久性连接,这个问题 产生的破碎的脚本将会返回一个错误的Content-Length(有比指定的字节更多的发送)。不幸的是,在某些情况下,这个不能在错误响应后来侦测, 只能在下一次之前。所以HttpClient必须以这种方式跳过那些多余的行。这个参数期望得到一个java.lang.Integer类型的值。0是不 允许在状态行之前的所有垃圾/空行。使用java.lang.Integer#MAX_VALUE来设置不限制的数字。如果这个参数没有被设置那就假设是 不限制的。
安全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)
SSL/TLS的定制
HttpClient使用SSLSocketFactory来创建SSL连接。SSLSocketFactory允许高度定制。它可以使用javax.net.ssl.SSLContext的实例作为参数,并使用它来创建定制SSL连接。
TrustManager easyTrustManager = new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { // 哦,这很简单! } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { //哦,这很简单! } @Override public 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);
协议模式
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代理配置
DefaultHttpClient httpclient = new DefaultHttpClient(); HttpHost proxy = new HttpHost("someproxy", 8080); httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
HTTP连接管理器
HTTP 连接是复杂的,有状态的,线程不安全的对象需要正确的管理以便正确地执行功能。HTTP连接在同一时间仅仅只能由一个执行线程来使用。 HttpClient采用一个特殊实体来管理访问HTTP连接,这被称为HTTP连接管理器,代表了ClientConnectionManager接口。一个HTTP连接管理器的目的是作为工厂服务于新的HTTP连接,管理持久连接和同步访问持久连接来确保同一时间仅有一个线程可以访问一个连接。
SingleClientConnManager 是一个简单的连接管理器,在同一时间它仅仅维护一个连接。尽管这个类是线程安全的,但它应该被用于一个执行线程。ThreadSafeClientConnManager 是一个复杂的实现来管理客户端连接池,它也可以从多个执行线程中服务连接请求。对每个基本的路由,连接都是池管理的。
HttpParams params = new BasicHttpParams(); ConnManagerParams.setTimeout(params, 2000); HttpConnectionParams.setConnectionTimeout(params, 10000); HttpConnectionParams.setSoTimeout(params, 20000); 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 将会分配基于它的配置的连接。如果对于给定路由的所有连接都被租出了,那么连接的请求将会阻塞,直到一个连接被释放回连接池。
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方法的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(); } // 合并线程 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; } @Override public 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(); } } }
连接收回策略
使用专用的监控线 程来收回因为长时间不活动而被认为是过期的连接。监控线程可以周期地调用 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; } @Override public 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服务器配置了在特定不活动周期之后丢掉持久连接来保存系统资源,往往这是不通知客户端的。
DefaultHttpClient httpclient = new DefaultHttpClient(); httpclient.setKeepAliveStrategy(new ConnectionKeepAliveStrategy() { public long getKeepAliveDuration(HttpResponse response, HttpContext context) { // 兑现'keep-alive'头部信息 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( ExecutionContext.HTTP_TARGET_HOST); if ("www.naughty-server.com".equalsIgnoreCase(target.getHostName())) { // 只保持活动5秒 return 5 * 1000; } else { // 否则保持活动30秒 return 30 * 1000; } } });
分享到: | |
没有评论