HttpComponents-Client学习

HttpComponents-Client 学习

点击进入 我的为知笔记链接(带格式)

前言

HTTP大概是今天网络上最引人注目的协议。
虽然java.net package中提供了基本的功能来通过HTTP获取资源,但它没有提供很多应用需要的完全的弹性或者功能性。HttpClient寻求填补这个空白,提供一种有效的、最新的、富功能的package 来实现客户端侧的大多数最新的HTTP标准和推荐。

1 HttpClient 范围

  • 基于HttpCore的客户端侧HTTP传输库
  • 基于阻塞式I/O
  • 内容不可知

2 HttpClient不是什么

不是一个浏览器。只是一个客户端侧的HTTP传输库。目的是发送/接收HTTP message。
不会试图处理content,不会试图执行js,但会尝试猜测content type -- 如果没有明确指定的话,或者重新格式化请求/重写location URI,或者其他无关于HTTP传输的功能。
 

第一章 基础

1.1 request execution   请求的执行  

HttpClient最基础的功能就是执行HTTP methods。一个HTTP method的执行 涉及到一个或多个HTTP request/response,通常由HttpClient内部处理。
用户只要提供一个request object来执行,HttpClient会发送request到目标服务器,正常的话服务器会返回response object,或者,如果没有成功执行 就抛出一个异常。
 
相当自然的,HttpClient API的入口就是HttpClient接口,其定义了上面描述的约定。
这里是一个例子,request execution的过程,最简单的形式:
1 CloseableHttpClient httpclient = HttpClients.createDefault();
2 HttpGet httpget = new HttpGet("http://localhost/");
3 CloseableHttpResponse response = httpclient.execute(httpget);
4 try {
5     <...>
6 } finally {
7     response.close();
8 }
 

1.1.1 HTTP request  

HttpClient 天然支持HTTP/1.1中所有的HTTP methods。每种method都有一个相应的类:HttpGet、HttpHead、HttpPost、HttpPut、HttpDelete、HttpTrace、HttpOptions。-- 见HttpCore的学习。
 
HttpClient提供了URIBuilder 工具类来简化request URI的创建和修改。
 1 URI uri = new URIBuilder()
 2     .setScheme("http")
 3     .setHost("www.google.com")
 4     .setPath("/search")
 5     .setParameter("q", "httpclient")
 6     .setParameter("btnG", "Google Search")
 7     .setParameter("aq", "f")
 8     .setParameter("oq", "")
 9     .build();
10 HttpGet httpget = new HttpGet(uri);
11 System.out.println(httpget.getURI());
 

1.1.2 HTTP response

请查看HttpComponents-Core中相关的部分。
1 HttpResponse response = new asicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
2 System.out.println(response.getProtocolVersion());
3 System.out.println(response.getStatusLine().getStatusCode());
4 System.out.println(response.getStatusLine().getReasonPhrase());
5 System.out.println(response.getStatusLine().toString());
 

1.1.3 处理message headers

 
请参考HttpComponents-Core中部分。

1.1.4 HTTP entity

请参考HttpComponents-Core中部分。

1.1.5 确保释放low level resources

 1 CloseableHttpClient httpclient = HttpClients.createDefault();
 2 HttpGet httpget = new HttpGet("http://localhost/");
 3 CloseableHttpResponse response = httpclient.execute(httpget);
 4 try {
 5     HttpEntity entity = response.getEntity();
 6     if (entity != null) {
 7         InputStream instream = entity.getContent();
 8         try {
 9             // do something useful
10         } finally {
11             instream.close();
12         }
13     }
14 } finally {
15     response.close();
16 }
关闭content stream 和 关闭response的区别在于:前者会试图保持底层的连接,后者则会关掉并遗弃该连接。<TODO>
 
有一些情况,当需要获取整个response content的很少一部分,并且,消费剩余的content具有性能惩罚,让连接重用太高,这种情况下,用户可以通过关闭response来结束content stream。(说啥了??)
 1 CloseableHttpClient httpclient = HttpClients.createDefault();
 2 HttpGet httpget = new HttpGet("http://localhost/");
 3 CloseableHttpResponse response = httpclient.execute(httpget);
 4 try {
 5     HttpEntity entity = response.getEntity();
 6     if (entity != null) {
 7         InputStream instream = entity.getContent();
 8         int byteOne = instream.read();
 9         int byteTwo = instream.read();
10         // Do not need the rest 不需要剩下的部分
11     }
12 } finally {
13     response.close();
14 }

1.1.6 消费entity content

消费entity content的推荐方式是使用HttpEntity#getContent() 或者 HttpEntity#writeTo(OutputStream)方法。
HttpClient 也提供了EntityUtils类,可以直接获取整个content body,以String、byte array形式。然后,除非你response entity来自一个受信任的HTTP server,并且已知限制长度,否则极其不推荐使用。
 1 CloseableHttpClient httpclient = HttpClients.createDefault();
 2 HttpGet httpget = new HttpGet("http://localhost/");
 3 CloseableHttpResponse response = httpclient.execute(httpget);
 4 try {
 5     HttpEntity entity = response.getEntity();
 6     if (entity != null) {
 7         long len = entity.getContentLength();
 8         if (len != -1 && len < 2048) {
 9         System.out.println(EntityUtils.toString(entity));
10         } else {
11         // Stream content out
12         }
13     }
14 } finally {
15 response.close();
16 }
 
某些情况下,可能有必要能够多次读取entity content -- 可重复读取 <TODO>。这种情况下, entity content必须以某种形式buffer一下,或者在内存中,或者在磁盘中。最简单的方式就是使用BufferedHttpEntity来包装一下。
1 CloseableHttpResponse response = <...>
2 HttpEntity entity = response.getEntity();
3 if (entity != null) {
4     entity = new BufferedHttpEntity(entity);
5 }
 

1.1.7 生成entity content

HttpClient提供了几个类,可以有效地通过HTTP连接stream out content。这些类的实例 可以关联到包含entity的request,例如POST、PUT,以在发出的HTTP request中包含entity content。
HttpClient提供了几个类用于最常见的数据容器,例如字符串、byte array、input stream、file:StringEntity、ByteArrayEntity、InputStreamEntity、FileEntity。
1 File file = new File("somefile.txt");
2 FileEntity entity = new FileEntity(file,
3 ContentType.create("text/plain", "UTF-8"));
4 HttpPost httppost = new HttpPost("http://localhost/action.do");
5 httppost.setEntity(entity);
 

1.1.7.1 HTML forms  表单 <TODO>

很多应用需要模拟提交HTML表单的过程,例如 为了登录某个web应用,或者提交input数据。HttpClient提供了UrlEncodedFormEntity来促进该过程:
1 List<NameValuePair> formparams = new ArrayList<NameValuePair>();
2 formparams.add(new BasicNameValuePair("param1", "value1"));
3 formparams.add(new BasicNameValuePair("param2", "value2"));
4 UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, Consts.UTF_8);
5 HttpPost httppost = new HttpPost("http://localhost/handler.do");
6 httppost.setEntity(entity);
 

1.1.7.2 Content chunking

一般,推荐让HttpClient自行选择最合适的传输编码 -- 基于要传输的HTTP message的properties。
然而,也可以告知HttpClient 推荐chunk编码 -- 通过设置HttpEntity#setChunked()。
请注意,HttpClient 仅会将这个flag视为一个暗示 <TODO>。当使用不支持chunk coding的协议版本时,该值会被忽略,如HTTP/1.0。
1     StringEntity entity = new StringEntity("important message",
2     ContentType.create("plain/text", Consts.UTF_8));
3     entity.setChunked(true);
4     HttpPost httppost = new HttpPost("http://localhost/acrtion.do");
5     httppost.setEntity(entity);
 

1.1.8 Response handlers   响应处理器 <TODO>

最简单的和最有效的处理响应的方式,是使用ResponseHandler接口,它有一个handleResponse(response)放那功夫。该方法完全解放了用户,不必再担心连接管理问题。
当使用一个ResponseHandler时,HttpClient会自动注意确保连接释放到连接管理器,无论请求执行成功 还是导致了异常。
 1 CloseableHttpClient httpclient = HttpClients.createDefault();
 2 HttpGet httpget = new HttpGet("http://localhost/json");
 3 ResponseHandler<MyJsonObject> rh = new ResponseHandler<MyJsonObject>() {
 4     @Override
 5     public JsonObject handleResponse(final HttpResponse response) throws IOException {
 6         StatusLine statusLine = response.getStatusLine();
 7         HttpEntity entity = response.getEntity();
 8         if (statusLine.getStatusCode() >= 300) {
 9             throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
10         }
11         if (entity == null) {
12             throw new ClientProtocolException("Response contains no content");
13         }
14         Gson gson = new GsonBuilder().create();
15         ContentType contentType = ContentType.getOrDefault(entity);
16         Charset charset = contentType.getCharset();
17         Reader reader = new InputStreamReader(entity.getContent(), charset);
18         return gson.fromJson(reader, MyJsonObject.class);
19     }
20 };
21 MyJsonObject myjson = client.execute(httpget, rh);
 

1.2 HttpClient 接口

该接口代表了HTTP request execution的大多数的基本约定。
它没有给处理request execution强加任何限制 或者专门的细节,诸如连接管理、状态管理、认证和重定向管理 都留给特定的实现去处理。这样,更易于使用更多的功能来装饰该接口,如content caching。
 
一般,HttpClient的实现们扮演了一个门面(facade),可以导向很多个特定目的的handler或者strategy接口实现 -- 用于处理HTTP protocol的某个特定方面,如重定向或者认证处理 或者决定连接是否保存。这使得用户可以有选择的替换掉默认的实现 -- 使用自定义的。
 1 ConnectionKeepAliveStrategy keepAliveStrat = new DefaultConnectionKeepAliveStrategy() {
 2     @Override
 3     public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
 4         long keepAlive = super.getKeepAliveDuration(response, context);
 5         if (keepAlive == -1) {
 6             // Keep connections alive 5 seconds if a keep-alive value
 7             // has not be explicitly set by the server
 8             keepAlive = 5000;
 9         }
10         return keepAlive;
11     }
12 };
13 CloseableHttpClient httpclient = HttpClients.custom().setKeepAliveStrategy(keepAliveStrat).build();
 

1.2.1 HttpClient 线程安全性 <TODO>

HttpClient的实现们应该是线程安全的。推荐 该类的同一个实例被复用到多个request execution。
 

1.2.2 HttpClient 资源的释放

当不再需要CloseableHttpClient的某个实例,且该实例将要退出其关联的连接管理器的范围之外时,必须通过调用CloseableHttpClient#close()方法来关闭。
1 CloseableHttpClient httpclient = HttpClients.createDefault();
2 try {
3     <...>
4 } finally {
5     httpclient.close();
6 }
 

1.3 HTTP execution context

见HttpComponents-Core学习。
HttpContext。
在HTTP request execution 过程中,HttpClient 为execution context添加了如下attributes:
  • HttpConnection实例,代表到目标服务器的实际连接。
  • HttpHost实例,代表连接目标。
  • HttpRoute实例,代表完整的连接route。<TODO>
  • HttpRequest实例,代表实际的HTTP request。在execution context中最终的HttpRequest对象,总是代表了发送到目标服务器的message state。默认情况下,HTTP/1.0 和 HTTP/1.1 使用相对的request URI。然而,如果request是通过proxy发送,在non-tuneling模式下,那URI就是绝对的。
  • HttpResponse实例,代表了实际的HTTP response。
  • Boolean对象,一个flag,用于指示实际请求是否被完全地发送到了连接目标。
  • RequestConfig对象,代表了实际的请求配置。
  • List<URI>对象,代表了在request execution过程中接收到的所有重定向地址的集合。
用户可以使用HttpClientContext adapter类来简化与context state的交互。
1 HttpContext context = <...>
2 HttpClientContext clientContext = HttpClientContext.adapt(context);
3 HttpHost target = clientContext.getTargetHost();
4 HttpRequest request = clientContext.getRequest();
5 HttpResponse response = clientContext.getResponse();
6 RequestConfig config = clientContext.getRequestConfig();
我的备注:我猜,其实就是加了一些方法,可以获取HttpClient添加的attribute。如果用HttpContext的话,就只能自己输入key了?
 
代表一个逻辑相关的session的多个请求序列,应该放到相同的HttpContext实例中执行,以确保在requests直接自动传播对话上下文和状态信息。
在下面的例子中,由初始的request设置的request configuration,会被保存在execution context中,并被传播到同一个context的后续的requests。<TODO>
 1 CloseableHttpClient httpclient = HttpClients.createDefault();
 2 RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(1000).setConnectTimeout(1000).build();
 3 HttpGet httpget1 = new HttpGet("http://localhost/1");
 4 httpget1.setConfig(requestConfig);
 5 CloseableHttpResponse response1 = httpclient.execute(httpget1, context);
 6 try {
 7     HttpEntity entity1 = response1.getEntity();
 8 } finally {
 9     response1.close();
10 }
11 HttpGet httpget2 = new HttpGet("http://localhost/2");
12 CloseableHttpResponse response2 = httpclient.execute(httpget2, context);
13 try {
14     HttpEntity entity2 = response2.getEntity();
15 } finally {
16     response2.close();
17 }
 

1.4 HTTP 协议拦截器

见HttpComponents-Core学习。
下面的例子,示意了如何在多个相关联的requests之间共享数据:
 1 CloseableHttpClient httpclient = HttpClients.custom().addInterceptorLast(new HttpRequestInterceptor() { // 添加拦截器
 2     public void process(final HttpRequest request, final HttpContext context)
 3             throws HttpException, IOException {
 4         AtomicInteger count = (AtomicInteger) context.getAttribute("count");
 5         request.addHeader("Count", Integer.toString(count.getAndIncrement()));
 6     }
 7 }).build();
 8 AtomicInteger count = new AtomicInteger(1);
 9 HttpClientContext localContext = HttpClientContext.create();
10 localContext.setAttribute("count", count); // 初始化数据
11 HttpGet httpget = new HttpGet("http://localhost/");
12 for (int i = 0; i < 10; i++) {
13     CloseableHttpResponse response = httpclient.execute(httpget, localContext);
14     try {
15         HttpEntity entity = response.getEntity();
16     } finally {
17         response.close();
18     }
19 }
20 // 缺省了一步 localContext.getAttribute("count")
 

1.5 异常处理

HTTP 协议处理器们,可能抛出两种类型的异常IOException 和 HttpException。
见HttpComponents-Core学习。
 

1.5.1 HTTP传输安全性

要理解 HTTP协议并不能很好地适合所有类型的应用,这很重要。
例如,不支持事务操作。
要确保HTTP传输层安全性,系统必须确保HTTP methods在应用层上的幂等性(idempotency)。

1.5.2 幂等methods

HTTP/1.1 spec定义了幂等method:除了error或者失效问题外,N次request的结果等同于一次request的结果。
 
默认,HttpClient 认定只有无entity的methods才是幂等的,例如GET/HEAD。而带有entity的则不是,如POST/PUT,这是出于兼容性考虑。
 

1.5.3 自动的异常恢复

默认,HttpClient会试图自动恢复I/O 异常。默认的自动恢复机制 局限于几个异常:
  • HttpClient不会试图恢复任何逻辑的或者HTTP协议错误(派生自HttpException)。
  • HttpClient会自动地重试那些幂等的方法。
  • HttpClient会自动的重试那些 HTTP request仍被发送中的传输异常(例如 request没有完全发送)。
 

1.5.4 request retry handler    请求重试处理器

想要启用自定义的异常恢复机制,用户应该提供HttpRequestRetryHandler接口的实现。<TODO>
 1 HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {
 2     public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
 3         if (executionCount >= 5) {
 4             // Do not retry if over max retry count
 5             return false;
 6         }
 7         if (exception instanceof InterruptedIOException) {
 8             // Timeout
 9             return false;
10         }
11         if (exception instanceof UnknownHostException) {
12             // Unknown host
13             return false;
14         }
15         if (exception instanceof ConnectTimeoutException) {
16             // Connection refused
17             return false;
18         }
19         if (exception instanceof SSLException) {
20             // SSL handshake exception
21             return false;
22         }
23         HttpClientContext clientContext = HttpClientContext.adapt(context);
24         HttpRequest request = clientContext.getRequest();
25         boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
26         if (idempotent) {
27             // Retry if the request is considered idempotent
28             return true;
29         }
30         return false;
31     }
32 };
33 CloseableHttpClient httpclient = HttpClients.custom().setRetryHandler(myRetryHandler).build();
请注意,用户可以使用StandardHttpRequestRetryHandler 来代替默认的,以便安全地自动重试RFC-2616中定义的幂等methods:GET, HEAD, PUT, DELETE, OPTIONS, TRACE。<TODO>
 

1.6 放弃request  <TODO>

某些情况下,HTTP request execution不能在预期的时间内完成,这可能是由于目标服务器的高荷载,或者是由于客户端侧的大量并发请求导致的。这种情况下,永久的结束request,并解锁阻塞的执行线程,是很重要的。
HttpClient执行的HTTP requests,可以在任何执行阶段 通过调用HttpUriRequest#abort() 方法 来放弃。
该方法是线程安全的,可以由任何线程调用。
当一个HTTP request被放弃之后,它的执行线程--哪怕正在阻塞中--也会被释放,会抛出一个InterruptedIOException。
 

1.7 重定向处理

HttpClient 会自动处理所有类型的重定向,除了那些HTTP spec明确禁止的 -- 这需要用户的介入。
对于POST、PUT requests 返回的 303 See Other 重定向,会被转换成GET requests -- HTTP spec要求的。
用户可以使用一个自定义的重定向策略来放宽HTTP spec对POST请求的重定向的限制。
1 LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();
2 CloseableHttpClient httpclient = HttpClients.custom().setRedirectStrategy(redirectStrategy).build();
 
HttpClient经常需要重写request message。默认,HTTP/1.0和HTTP/1.1,会使用相对的request URIs。类似的,原始的request 可能被多次重定向。最终的HTTP地址,可以使用原始的request和context来构建。可以使用工具方法 URIUtils#resolve() 。<TODO>
 1 CloseableHttpClient httpclient = HttpClients.createDefault();
 2 HttpClientContext context = HttpClientContext.create();
 3 HttpGet httpget = new HttpGet("http://localhost:8080/");
 4 CloseableHttpResponse response = httpclient.execute(httpget, context);
 5 try {
 6     HttpHost target = context.getTargetHost();
 7     List<URI> redirectLocations = context.getRedirectLocations(); // 为嘛??
 8     URI location = URIUtils.resolve(httpget.getURI(), target, redirectLocations);
 9     System.out.println("Final HTTP location: " + location.toASCIIString());
10     // Expected to be an absolute URI
11 } finally {
12     response.close();
13 }
 

第二章 连接管理

2.1 连接持久化

创建从一个主机到另一个主机的连接,是一个很复杂的过程,涉及到多组 介于两个端点间的packet交换 -- 可能相当耗时。连接握手的overhead(管理费用?) 可能相当明显,特别是对于小的HTTP messages来说。如果已经打开的连接可以给多个requests重复使用的话,用户可以获得一个很高的数据吞吐量。
 
默认,HTTP/1.1 就可以重用HTTP连接。HTTP/1.0端点也可以使用一种机制来显式地交流他们的首选项 来保持连接alive,并将其用于多个requests。HTTP agents也可以在一段特定时间内保持空闲连接alive,以防后续的requests也需要访问同一个目标主机。保持连接alive的能力 通常被叫做 连接持久性。
 
HttpClient完全支持链接持久化。
 

2.2 HTTP connection routing   连接路由?

HttpClient 能够创建到目标主机的连接,可以是直接的,或者通过一个路由 -- 可能涉及多个中间级连接,这个中间级连接也被叫做hops。 Httpclient 将一个路由的连接 划分成plain、tuneled、以及layered。使用到tunnel连接的多个中间级代理,也叫proxy chaining。
plain route  
tunneled route
layered route
原文:
    HttpClient is capable of establishing connections to the target host either directly or via a route that may involve multiple intermediate connections - also referred to as hops. HttpClient differentiates connections of a route into plain, tunneled and layered. The use of multiple intermediate proxies to tunnel connections to the target host is referred to as proxy chaining.

Plain routes are established by connecting to the target or the first and only proxy. Tunnelled routes are established by connecting to the first and tunnelling through a chain of proxies to the target. Routes without a proxy cannot be tunnelled. Layered routes are established by layering a protocol over an existing connection. Protocols can only be layered over a tunnel to the target, or over a direct connection without proxies.

 

2.2.1 route computation   路由计算

RouteInfo接口 代表了
HttpRoute是RouteInfo的一个强有力的实现,不可修改。
HttpTracker是RouteInfo的一个可修改的实现。
HttpRouteDirector是一个帮助类,可被用于计算出route中的下一步。
 

2.2.2 secure HTTP connection

...
 

2.3 HTTP connection managers   连接管理者  <TODO>

2.3.1 被管理的连接 和 连接管理者

 
 
>>>>>>>>>>>>>>>>>>>>>未完待续<<<<<<<<<<<<<<<<<<<<<
 
 
 
 
 
原文地址:https://www.cnblogs.com/larryzeal/p/7079242.html