【HttpClient4.5中文教程】十.HttpClient连接管理2

x33g5p2x  于2021-12-28 转载在 其他  
字(8.0k)|赞(0)|评价(0)|浏览(284)

4.请求执行的多线程

当配备连接池管理器时,比如 PoolingClientConnectionManager, HttpClient 可以被用使用多线程来同时执行多个请求。

PoolingClientConnectionManager将会基于它的配置来分配连接。如果对于给定路由的所有连接都被使用了,那么连接的请求将会阻塞,直到一个连接被释放回连接池。 它
可以通过设置'http.conn-manager.timeout'为一个正数来保证连接管理器不会在连接请求执行时无限期的被阻塞。 如果连接请求不能在给定的时间内被响应,将会抛出
ConnectionPoolTimeoutException 异常。

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

一个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
    }
  }
}

5.连接回收策略Connection eviction policy

一个典型阻塞 I/O 的模型的主要缺点是网络socket仅当 I/O 操作阻塞时才可以响应 I/O
事件。当一个连接被释放回管理器时,它可以被保持活动状态而却不能监控socket的状态
和响应任何 I/O 事件。 如果连接在服务器端关闭,那么客户端连接也不能去侦测连接状态中的变化(也不能关闭本端的socket去作出适当反馈)。

HttpClient 通过测试连接是否是连接是过时的来尝试去减轻这个问题。在服务器端关闭的连接已经不再有效了,优先使用之前使用执行 HTTP 请求的连接。过时的连接检查也并不是100%可靠。唯一可行的而不涉及每个空闲连接的socket模型线程的解决方案是:是使用专用的监控线程来收回因为长时间不活动而被认 为 是 过 期 的 连 接 。 监 控 线 程 可 以 周 期 地 调 用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();
    }
  }
}

6.连接保持策略

HTTP 规范没有详细说明一个持久连接可能或应该保持活动多长时间。一些 HTTP 服务器使用非标准的首部Keep-Alive来告诉客户端它们想在服务器端保持连接活动的周期秒数。如果这个信息存在, HttClient 就会使用它。如果响应中首部Keep-Alive 在不存在, HttpClient会假定无限期地保持连接。然而许多 HTTP 服务器的普遍配置是在特定非
活动周期之后丢掉持久连接来保护系统资源,往往这是不通知客户端的。默认的策略是过于乐观的,你必须提供一个自定义的keep-alive策略

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

7.连接socket工厂

HTTP连接内部使用java.net.Socket 来通过网线处理传输数据。可是,他们依靠ConnectionSocketFactory接口来创建、初始化和连接socket。HttpClient为使用者提供了在程序运行时明确的socket初始化代码。PlainConnectionSocketFactory是创建,初始化普通的socket(非加密socket)的工厂。

socket的创建过程和把它连接到一个主机上的过程是非对称的 。所以当被连接操作阻塞时socket将会关闭。

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(netAddress.getByAddress(new byte[] {127,0,0,1}), 80);
sf.connectSocket(timeout, socket, target, remoteAddress, null, clientContext);

7.1.安全套接字分层

LayeredConnectionSocketFactory是ConnectionSocketFactory接口的拓展。分层的socket工厂可以创建在已经存在的普通socket之上。套接字分层主要通过代理来创建安全的socket。 HttpClient装载实现了 SSL/TLS 分层的 SSLSocketFactory。 请注意 HttpClient 不使用任
何自定义加密功能。它完全依赖于标准的Java Cryptography(JCE)和Secure Sockets(JSEE)扩展。

7.2.与连接管理器整合

自定义的连接socket工厂与一个被称为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();

7.3.定制SSL/TLS

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

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

SSLSocketFactory的定制意味着对SSL/TLS协议概念有一定的熟悉,详细说明它超出
了本文档的范围。请参考Java™Secure Socket Extension(JSSE) 参考指南[http://java.sun.com/j2se/1.5.0/docs/guide/security/jsse/JSSERefGuide.html],从而获得javax.net.ssl.SSLContext的详细描述和相关工具。

7.4.主机名核实

为了信任证明并且客户端的表现在SSL/TLS级别上,HttpClient随意的核实是否目标主机名匹配储存在X.509服务器上的证书。一旦连接被建立,这个证明能提供服务器信任材料额外的保证。javax.net.ssl.HostnameVerifier接口代表了主机名验证的策略。 HttpClient有两个
javax.net.ssl.HostnameVerifier的实现。重点:主机名验证不应该和SSL信任验证混淆。

·DefaultHostnameVerifier:遵从RFC 2818规范,被HttpClient默认实现。主机名必须根据规定的证书匹配被几个可选择名字,如果没有合适的名字,将会给出最具体的证书CN 。一个通配符可能会出现在CN中和任何可选择的地方。
【he default implementation used by HttpClient is expected to becompliant with RFC 2818. The hostname must match any of alternative names specified by thecertificate, or in case no alternative names are given the most specific CN of the certificate subject.A wildcard can occur in the CN, and in any of the subject-alts.】

·NoopHostnameVerifier:主机名验证器根本就关掉了主机名验证。他接受任何合法的SSL会话,并且匹配目标主机

每次HttpClient默认使用DefaultHostnameVerifier实现。如果必要的话,你可以制定一个不同的主机名认证器实现。

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

在HttpClient 4.4 版本,使用被Mozilla Foundation维护的公共的后缀列表,确保SSL证书中的通配符不能够被滥用来在顶级域名下启用多个域名。HttpClient在发布时复制了一份列表。
最新的版本列表能够在https://publicsuffix.org/list/ [https://publicsuffix.org/list/
effective_tld_names.dat]找到。强烈建议下载一份列表,防止在每天使用时访问原始位置。

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

8.HttpClient代理配置

尽管HttpClient了解复杂的路由模式和路由链,但它只支持简单的重定向或者一跳代理连接。
告诉HttpClient通过代理连接目标服务器最简单的方式是设置默认的代理参数
HttpHost proxy = new HttpHost("someproxy", 8080);
DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
CloseableHttpClient httpclient = HttpClients.custom() .setRoutePlanner(routePlanner) .build();

你也可以使用标准的JRE代理选择器通知HttpClient来获得代理信息

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

相关文章