Java HttpClient(一:基础对象、类说明)

参考文献:http://hc.apache.org/
http://hc.apache.org/httpcomponents-client-ga/tutorial/html/
http://hc.apache.org/httpcomponents-client-ga/examples.html

HTTP 协议可能是现在 Internet 上使用得最多、最重要的协议了,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源。虽然在 JDK 的 java.net 包中已经提供了访问 HTTP 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活。

文章目录

    1. HttpComponents
    1. HttpClient简介
  • 2.1 特点

  • 2.1.1 特点 * 2.1.2 基本使用步骤

    • 2.2 请求执行过程与相关对象
  • 2.2.1 请求对象:HttpXXX(根据请求的方法进行分类,如HttpGet,HttpPost) * 2.2.2 HTTP 响应对象 * 2.2.3 Header * 2.2.4 Http entity * 2.2.5 Response handlers * 2.2.6 HttpClient接口 * 2.2.6.1 **关于HttpClien接口实现类需要满足的特性**

    1. HTTP execution context
    1. HTTP protocol interceptors
    1. Exception处理
    1. 取消请求与 处理重定向

1. HttpComponents

Apache HttpComponents项目旨在创建、维护一组低层次的用于Java的HTTP及其先关协议组件。

HttpComponents可用于:

  1. HTTP client应用;
  2. HTTP Server应用,例如web浏览器、web爬虫,HTTP代理,Web服务层库,或是基于HTTP的扩展协议。

HttpComponents 架构
HttpComponents 包括:HttpCore 、HttpClient 、Asynch HttpClient 、Commons HttpClient(被废弃的代码,不建议使用)

2. HttpClient简介

HTTPClient 是兼容HTTP/1.1的、基于HTTPCore的HTTP agent实现,支持认证、HTTP状态维护、HTTP连接管理。HTTPClient 是为了取代
Commons HttpClient 3.x而存在的,另外,强烈建议使用Commons HttpClient 的用户升级到HTTPClient。

虽然java.net包中提供了基础的HTTP方法,但是缺乏伸缩性、同时缺乏完善的功能。HttpClient用于填补这个空白。

2.1 特点

2.1.1 特点

  1. Httpclient基于HttpCore实现;
  2. 基于典型的阻塞IO;
  3. 不处理具体的Http Content(不对Http 内容进行解析,这应该由应用解析处理)

注意:
4. HttpClient并不是一个浏览器,HttpClient是一个Http client端的开发库。
5. HttpClient只是用于发送、接收Http信息,并不会帮你处理信息里面的内容。其实,它也不知道怎么处理。比如服务器给你回复了一个response,里面有个1,这是表示1块钱,还是1百万,这是具体的调用应用才直到解析的。而HttpClient是一个通用的库。它存在的目的是你不用去使用socket,去连接server,一点一点拼装消息体。另外,Httpclient还有复杂的功能,如池化连接等。

2.1.2 基本使用步骤

使用HttpClient发送请求、接收响应很简单,一般需要如下几步即可。(需要导入httpclient-Versin.jar, httpcore-version.jar两个包。在使用过程中,有时候会抛出logging class not found 异常,这时可将commons-logging包导入工程即可)

创建HttpClient对象。

创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象。

如果需要发送请求参数,可调用HttpGet、HttpPost共同的setParams(HetpParams params)方法来添加请求参数;对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数。

调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个HttpResponse。

调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容。

释放连接。无论执行方法是否成功,都必须释放连接

2.2 请求执行过程与相关对象

HttpClient的最基本使用方法是:创建 请求对象,使用httpclient将该请求转交给Server,然后返回一个 response对象或是抛出异常。

上述过程使用代码就是:

1CloseableHttpClient httpclient = HttpClients.createDefault(); 2 3//创建请求对象 4HttpGet httpget = new HttpGet("http://localhost/"); 5 6//httpclient和server通信,将该请求转交给Server,并返回reposne 7CloseableHttpResponse response = httpclient.execute(httpget); 8try { 9 <...> 10} finally { 11 response.close(); 12} 13 14

2.2.1 请求对象:HttpXXX(根据请求的方法进行分类,如HttpGet,HttpPost)

HttpClient支持 HTTP/1.1规范定义的所有方法:GET, HEAD, POST, PUT, DELETE, TRACE 和OPTIONS。不同的方法,在HttpClient中,使用不同的类进行封装,代表不同的请求方法,对应的是HttpGet, HttpHead, HttpPost, HttpPut, HttpDelete, HttpTrace, 和HttpOptions。

构造请求对象时,需要传入一个资源定位符(URI)【 URI包括协议、主机名、端口(可选)、资源路径、查选参数(可选)、分片参数(可选)】,表示是对那个server的那个资源请求的。

例如:

1HttpGet httpget = new HttpGet( 2 "http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq="); 3 4//或是 5URI uri = new URIBuilder() 6 .setScheme("http") 7 .setHost("www.google.com") 8 .setPath("/search") 9 .setParameter("q", "httpclient") 10 .setParameter("btnG", "Google Search") 11 .setParameter("aq", "f") 12 .setParameter("oq", "") 13 .build(); 14HttpGet httpget = new HttpGet(uri); 15 16

2.2.2 HTTP 响应对象

Http reposne对象包含的内容包括:使用的协议的版本、Http 状态码、关联的文本内容。

1HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, 2HttpStatus.SC_OK, "OK"); 3 4System.out.println(response.getProtocolVersion()); //HTTP/1.1 5System.out.println(response.getStatusLine().getStatusCode()); //200 6System.out.println(response.getStatusLine().getReasonPhrase()); //OK 7System.out.println(response.getStatusLine().toString()); //HTTP/1.1 200 OK 8 9

2.2.3 Header

设置请求、响应头部信息。

1HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, 2 HttpStatus.SC_OK, "OK"); 3response.addHeader("Set-Cookie", 4 "c1=a; path=/; domain=localhost"); 5response.addHeader("Set-Cookie", 6 "c2=b; path=\"/\", c3=c; domain=\"localhost\""); 7 8Header h1 = response.getFirstHeader("Set-Cookie"); 9System.out.println(h1); //Set-Cookie: c1=a; path=/; domain=localhost 10Header h2 = response.getLastHeader("Set-Cookie"); 11System.out.println(h2); //Set-Cookie: c2=b; path="/", c3=c; domain="localhost" 12Header[] hs = response.getHeaders("Set-Cookie"); 13System.out.println(hs.length); //2 14 15

2.2.4 Http entity

Http Entity用于描述 Http请求或是响应中的内容(消息体、正文)。在Http规范中,Post和Put方法可以携带Http消息体,另外,Http Response中也可以包含一个消息体(除了 head方法的响应、或是状态码为204【无内容】,304【未修改】 205【内容重置】的响应。)。

HttpClient将上述不同场景下的entity分为三类:

streamed:从流中产生的或是运行中生成的。该类entity包括从http response中获取的。通常请求下,该类entity不是可重复的。

self-contained:内容来源与内存或是通过非连接的方式获取的(不是正常的过程http connection中获取的)。该类entity通常用于封装http request中的内容。(一般是可重复的)

wrapping:内容来源于另外一个entity对象;

关于entity 的可重复性

一个entity是可重复的,说明该entity可重复读取。只有self-contained是可重复的(例如,ByteArrayEntity或是StringEntity)。

entity怎么使用

entity可包括二进制数据也可以包括字符数据(支持设置字符编码)。当执行包含内容的request或是当获取到包含消息体的response时,就会创建出entity对象。

你可以通过如下方法获取到entity中内容:

  1. 通过HttpEntity.getContent() 方法从entity中获取java.io.InputStream 对象;

  2. 通过HttpEntity.writeTo(OutputStream)方法,一次性读取所有数据到指定的流中,然后,你可以从该流中获取数据。

  3. 另外,如果Entity对象是从外部接收的化(例如,从response中提取的)也可以通过HttpEntity.getContentType() 和HttpEntity.getContentLength()方法获取通用的entity的元数据,例如content-type,content-length(如果有的话,如果没有内容的化,content-length返回-1)。

  4. 如果entity是要发送出去的,则必须自己设置entity的content-type。例如:

1StringEntity myEntity = new StringEntity("important message", ContentType.create("text/plain", "UTF-8")); 2 3System.out.println(myEntity.getContentType()); //Content-Type: text/plain; charset=utf-8 4System.out.println(myEntity.getContentLength()); //17 5 6System.out.println(EntityUtils.toString(myEntity)); //important message 7 8System.out.println(EntityUtils.toByteArray(myEntity).length); //17 9 10

entity使用注意事项

如果通过流的方式获取entity中的数据,请确保读取完毕后,主动将流关闭

1CloseableHttpClient httpclient = HttpClients.createDefault(); 2HttpGet httpget = new HttpGet("http://localhost/"); 3CloseableHttpResponse response = httpclient.execute(httpget); 4try { 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(); //关闭流,但是底层的connection是alive的 12 } 13 } 14} finally { 15 response.close(); // 立即关闭,丢弃connection 16} 17 18

PS:关于strea.close与response的区别:

  1. instream.close():只关闭流,但是底层的connection是alive的
  2. response.close(): 立即关闭,丢弃connection

另外,使用HttpEntity#writeTo(OutputStream)方法也需要自己确保将底层的流关闭了。

如果你不想手动的处理流的化,你可以选择使用EntityUtils#consume(HttpEntity)类来获取数据,该方法可确保你将所有的数据读取出来了,然后帮你关闭底层的流。

另外,如果你只需要entity中的部分、少量的数据的话(读取其他的数据代价大,其他的剩下的数据量比较大)、且你不需要复用该http connection,那么你可以直接关闭该connection,不用理会底层的流(当关闭connection【通过colse response】,底层的流会自动关闭)。示例代码如下:

1CloseableHttpClient httpclient = HttpClients.createDefault(); 2HttpGet httpget = new HttpGet("http://localhost/"); 3CloseableHttpResponse response = httpclient.execute(httpget); 4try { 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 //忽略stream的关闭 12 } 13} finally { 14 response.close();//当response关闭时,关闭connection,关闭stream 15} 16 17

获取entity的完整内容

获取entity完整的内容的方法包括:

  1. 通过HttpEntity.getContent()返回的流读取;
  2. 通过HttpEntity.writeTo(OutputStream) 设置的流读取;
  3. 通过EntityUtils 的静态方法(不推荐使用,除非你信任该 entity内容源、且包含的数据量较小,否则不安全且效率慢)

其中第一、二种方法是推荐的方法。EnityUtils的使用示例如下:

1CloseableHttpClient httpclient = HttpClients.createDefault(); 2HttpGet httpget = new HttpGet("http://localhost/"); 3CloseableHttpResponse response = httpclient.execute(httpget); 4try { 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} 17 18

填充entity的内容(用于post与put)

你可以从字符串String、字节数组byte array、输入流input stream、文件中获取获取内容填充enttiy对象。HttpClient中使用StringEntity, ByteArrayEntity, InputStreamEntity, and FileEntity来表示内容来源不同的entity。

1File file = new File("somefile.txt"); 2FileEntity entity = new FileEntity(file, 3 ContentType.create("text/plain", "UTF-8")); 4 5HttpPost httppost = new HttpPost("http://localhost/action.do"); 6httppost.setEntity(entity); 7 8

2.2.5 Response handlers

最简单的处理response的方法是使用hander接口,该接口包含一个方法 handleResponse(HttpResponse response)。

使用response handler的好处是你可以在方法中获取数据,但是不同关系connection的管理问题。Httpclient会自动的将connection释放,还给connection manager(不管请求是否执行成功失败或是抛出了异常)

1CloseableHttpClient httpclient = HttpClients.createDefault(); 2HttpGet httpget = new HttpGet("http://localhost/json"); 3 4ResponseHandler<MyJsonObject> rh = new ResponseHandler<MyJsonObject>() { 5 6 @Override 7 public JsonObject handleResponse( 8 final HttpResponse response) throws IOException { 9 StatusLine statusLine = response.getStatusLine(); 10 HttpEntity entity = response.getEntity(); 11 if (statusLine.getStatusCode() >= 300) { 12 throw new HttpResponseException( 13 statusLine.getStatusCode(), 14 statusLine.getReasonPhrase()); 15 } 16 if (entity == null) { 17 throw new ClientProtocolException("Response contains no content"); 18 } 19 Gson gson = new GsonBuilder().create(); 20 ContentType contentType = ContentType.getOrDefault(entity); 21 Charset charset = contentType.getCharset(); 22 Reader reader = new InputStreamReader(entity.getContent(), charset); 23 return gson.fromJson(reader, MyJsonObject.class); 24 } 25}; 26 27 28MyJsonObject myjson = client.execute(httpget, rh); 29 30

2.2.6 HttpClient接口

HttpClient是对Http请求中执行的抽象表示。它对请求处理执行过程没有添加任何限制,将对连接管理、状态维护、认证、重定向的处理交给具体的实现类。

通常来说,HttpClient实现类采用门面模式,将一系列的handler与策略组合起来。
例如:

1ConnectionKeepAliveStrategy keepAliveStrat = new DefaultConnectionKeepAliveStrategy() { 2 3 @Override 4 public long getKeepAliveDuration( 5 HttpResponse response, 6 HttpContext context) { 7 long keepAlive = super.getKeepAliveDuration(response, context); 8 if (keepAlive == -1) { 9 // Keep connections alive 5 seconds if a keep-alive value 10 // has not be explicitly set by the server 11 keepAlive = 5000; 12 } 13 return keepAlive; 14 } 15 16}; 17 18CloseableHttpClient httpclient = HttpClients.custom() 19 .setKeepAliveStrategy(keepAliveStrat) 20 .build(); 21 22

2.2.6.1 关于HttpClien接口实现类需要满足的特性

(1)HttpClient thread safety
HttpClient的实现应该是线程安全的。同时,推荐复用HttpClient对象,创建一个对象,处理一批的请求。

(2)HttpClient resource deallocation
当CloseableHttpClient 的实例对象不再需要时,则需要在适当的地方关闭该实例。

1CloseableHttpClient httpclient = HttpClients.createDefault(); 2try { 3 <...> 4} finally { 5 httpclient.close(); 6} 7 8

3. HTTP execution context

通常来说,Http该设计为一个 无状态的、请求-响应模式的协议。但是实际上,应用希望能够对Http状态进行都某些状态进行持久化处理。

为了应用能够对当前的处理状态进行维护,HttpClient允许在特定的执行上下文中处理Http请求 。多个逻辑相关的请求可以放到同一个逻辑session中。Http context功能和map<String,Object>类似,就是一个值类型的key-value集合。

HttpContext 可以包括任意类型的对象,因此在多线程之间共享httpcontext可能线程不安全的。推荐的作法是:某个线程都维护自己的一个httpcontext对象。

在http请求执行过程时,HttpClient会增加下列属性到httpcontext中:

  1. HttpConnection:表示实际的和目标server建立的connection对象;
  2. HttpHost :目标对象;
  3. HttpRoute :表示完整的连接路由对象;
  4. HttpRequest:表示实际的Http request对象;
  5. HttpResponse:表示实际的Http response对象;
  6. java.lang.Boolean:表示实际的request对象是否完全的被送到connection target了;
  7. RequestConfig:实际的request 配置;
  8. java.util.List object :表示在处理request过程中收到的所有的重定向请求地址;

可以HttpClientContext的适配器来简化HttpClient的处理:

1HttpContext context = <...> 2HttpClientContext clientContext = HttpClientContext.adapt(context); 3HttpHost target = clientContext.getTargetHost(); 4HttpRequest request = clientContext.getRequest(); 5HttpResponse response = clientContext.getResponse(); 6RequestConfig config = clientContext.getRequestConfig(); 7 8

4. HTTP protocol interceptors

Http协议拦截器是Http 协议面向方面编程中的一部分。通常来说,协议拦截器用于对具有某一种header或是某些相关的header的请求进行统一的处理。或是对response进行统一的header渲染,填充。另外,拦截器也可以对request、response的内容进行处理,例如透明的请求体内容的压缩、解压缩。

拦截器可以设置多个,多个拦截器依序执行,不同的拦截器可以通过Http Conext来进行信息共享。

通常来说,如果拦截器不依赖于特定的执行上下文,则当存在多个拦截器时,拦截器的执行顺序应该对最终结果没有影响。如果拦截器之间存在依赖关系,则拦截器必须按照特定的顺序进行注册,以限制拦截器的执行顺序。

拦截器必须实现为线程安全的。和servlet相同,拦截器不应该使用实例变量,除非对它们的访问都是进行了同步。

1CloseableHttpClient httpclient = HttpClients.custom() 2 .addInterceptorLast(new HttpRequestInterceptor() { 3 4 public void process( 5 final HttpRequest request, 6 final HttpContext context) throws HttpException, IOException { 7 AtomicInteger count = (AtomicInteger) context.getAttribute("count"); 8 request.addHeader("Count", Integer.toString(count.getAndIncrement())); 9 } 10 11 }) 12 .build(); 13 14AtomicInteger count = new AtomicInteger(1); 15HttpClientContext localContext = HttpClientContext.create(); 16localContext.setAttribute("count", count); 17 18HttpGet httpget = new HttpGet("http://localhost/"); 19for (int i = 0; i < 10; i++) { 20 CloseableHttpResponse response = httpclient.execute(httpget, localContext); 21 try { 22 HttpEntity entity = response.getEntity(); 23 } finally { 24 response.close(); 25 } 26} 27 28

5. Exception处理

在Http请求处理过程中,会抛出两类异常: java.io.IOException(如socket超时)、HttpException (Http协议处理异常)

在错误处理时,需要注意如下几点:

  1. http协议不是用户处理事务的。比如client发送了请求,server处理请求,返回response,但是client是否能收到resposne,server是不管的。server不会应为你收不到,无法做后续处理,就进行业务的回滚;
  2. 关于幂等性:幂等性由上层的server进行处理;
  3. Httpclient可以支持从IO Exception中自动恢复。但是对于Http 协议错误,HttpClient一般是不管的。

6. 取消请求与 处理重定向

(1)取消请求
可以通过HttpUriRequest.abort()方法将请求取消。该方法是线程安全的,可以同时被多个线程调用。如果线程处理(发起request)的线程阻塞在该请求的执行操作上,如果该请求被取消后,该线程会抛出InterruptedIOException异常。

(2) 处理重定向
HttpClient可以自动处理重定向(除了Http规范禁止的需要用户交互的重定向之外)。用户自定义自己的重定向处理策略,来处理请求中的重定向;

1LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy(); 2CloseableHttpClient httpclient = HttpClients.custom() 3 .setRedirectStrategy(redirectStrategy) 4 .build(); 5 6

代码交流 2021