HttpComponents-Client学习

HttpComponents-Client 学习

点击进入 个人为知笔记连接(带格式)
html

前言

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.java

 

 

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 被管理的链接 和 链接管理者

 
 
>>>>>>>>>>>>>>>>>>>>>未完待续<<<<<<<<<<<<<<<<<<<<<
相关文章
相关标签/搜索