HttpClient4.5教程-基础 1.1

本文深入讲解了HttpClient的基本功能,包括HTTP请求与响应处理、消息头管理、实体内容操作及资源释放等内容,并介绍了如何使用ResponseHandler简化响应处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.1 Request 的执行

HttpClient最必不可少的功能就是执行HTTP的方法,执行HTTP方法会涉及到一个或者多个HTTP request/HTTP response交换,而这些过程通常会在HttpClient内部完成。使用者提交一个request的对象去执行,HttpClient会发送这个request到目标服务器并且获得一个对应的response对象,如果不成功的话则抛出一个异常。

自然而然,满足了上面描述的HttpClient接口就是HttpClient API的主要入口。

下面是一个最简单的request执行过程:

[java]  view plain  copy
  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

所有的HTTP request都有一个请求线,包含了请求的方法名称,请求的URI和HTTP协议版本。

HttpClient开箱即用的支持所有HTTP/1.1中定义的HTTP方法,包括GET,HEAD,POST,PUT,DELETE,TRACE 和 OPTIONS,这些HTTP方法类型都有一个与之对应的类:HttpGet,HttpHead,HttpPost,HttpPut,HttpDelete,HttpTrace 和 HttpOptions。

Request-URI是一个统一资源定位符,它指明了用于处理该request的资源的位置。HTTP request URIs包含了一个协议类型,主机名,可选端口,资源路径,可选查询参数,可选片段。

[java]  view plain  copy
  1. HttpGet httpget = new HttpGet(  
  2.      "http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq=");  
  3. HttpClient提供URIBuilder实用类用于简化request URIS的创建和修改。  
  4. URI uri = new URIBuilder()  
  5.         .setScheme("http")  
  6.         .setHost("www.google.com")  
  7.         .setPath("/search")  
  8.         .setParameter("q""httpclient")  
  9.         .setParameter("btnG""Google Search")  
  10.         .setParameter("aq""f")  
  11.         .setParameter("oq""")  
  12.         .build();  
  13. HttpGet httpget = new HttpGet(uri);  
  14. System.out.println(httpget.getURI());  

输出:

[java]  view plain  copy
  1. http://www.google.com/search?q=httpclient&btnG=Google+Search&aq=f&oq=  

1.1.2 HTTP response

HTTP response是服务器在接收并且处理完request消息之后返回给客户端的消息,消息的第一行包含了协议版本,status code和与之关联的文本。

[java]  view plain  copy
  1. HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,   
  2. HttpStatus.SC_OK, "OK");  
  3.   
  4. System.out.println(response.getProtocolVersion());  
  5. System.out.println(response.getStatusLine().getStatusCode());  
  6. System.out.println(response.getStatusLine().getReasonPhrase());  
  7. System.out.println(response.getStatusLine().toString());  

输出:

[java]  view plain  copy
  1. HTTP/1.1  
  2. 200  
  3. OK  
  4. HTTP/1.1 200 OK  

1.1.3 消息头处理

HTTP消息包含多个header,这些header描述了消息的属性比如content length,content type等等,HttpClient提供了取出,添加,删除和遍历header的方法。

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

输出:

[java]  view plain  copy
  1. Set-Cookie: c1=a; path=/; domain=localhost  
  2. Set-Cookie: c2=b; path="/", c3=c; domain="localhost"  
  3. 2  

获取所有指定headers的最高效的方式是使用HeaderIterator接口。

[java]  view plain  copy
  1. HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");  
  2. response.addHeader("Set-Cookie""c1=a; path=/; domain=localhost");  
  3. response.addHeader("Set-Cookie""c2=b; path=\"/\", c3=c; domain=\"localhost\"");  
  4.   
  5. HeaderIterator it = response.headerIterator("Set-Cookie");  
  6.   
  7. while (it.hasNext()) {  
  8.     System.out.println(it.next());  
  9. }  

输出:

[java]  view plain  copy
  1. Set-Cookie: c1=a; path=/; domain=localhost  
  2. Set-Cookie: c2=b; path="/", c3=c; domain="localhost"  

该接口同时也提供HTTP消息转换到独立header元素的便利方法。

[java]  view plain  copy
  1. HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");  
  2. response.addHeader("Set-Cookie""c1=a; path=/; domain=localhost");  
  3. response.addHeader("Set-Cookie""c2=b; path=\"/\", c3=c; domain=\"localhost\"");  
  4.   
  5. HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator("Set-Cookie"));  
  6.   
  7. while (it.hasNext()) {  
  8.     HeaderElement elem = it.nextElement();   
  9.     System.out.println(elem.getName() + " = " + elem.getValue());  
  10.     NameValuePair[] params = elem.getParameters();  
  11.     for (int i = 0; i < params.length; i++) {  
  12.         System.out.println(" " + params[i]);  
  13.     }  
  14. }  

输出:

[java]  view plain  copy
  1. c1 = a  
  2. path=/  
  3. domain=localhost  
  4. c2 = b  
  5. path=/  
  6. c3 = c  
  7. domain=localhost  

1.1.4 HTTP entity

HTTP消息可以携带跟request和response相关联的content entity。由于这些Entity是可选的,所以他们在某些request和response中可以查找到,有些则查不到。使用了entity的request被称为entity封装请求,HTTP定义了两种entity封装请求方法:POST 和 PUT。Response通常会封装一个content entity。不同的方法会有对应的异常,比如对于HEAD来说就有204 No Content,304 Not Modified, 205 Reset Content异常。

HttpClient根据内容来源将Entities区分为3种,根据内容来源区分为:

streamed: 内容来自于流或者实时产生。特别的是,这个分类包含了从HTTP responses接收到的entities,Streamed entities通常不可重复。

self-contained: 内容来自于内存或者通过独立于connection或者其他entity的手段获得,self-contained entities通常可以重复,其也是封装了entity的HTTP request最常用的Entities类型。

wrapping : 内容来自于其他的entity。

这种区分对于HTTP response输出内容时的连接管理是非常重要的。对于request entites来说,由于其是被应用创建并且通过HttpClient发送,streamed和self-contained有什么不同就没有那么重要。

由此,建议将不可重复的entites视为streamed,可以重复的entities视为self-contained。


1.1.4.1 可重复的entities

一个entity能够重复,表明它的内容可以被读取多次,这种情况只会在self contained entities中发生(比如ByteArrayEntity 或者 StringEntity)


1.1.4.2 使用HTTP entities

Entity可以同时代表二进制和文字内容,支持对字符进行编码,比如对character content进行编码。

Entity产生于request(封装内容 )执行时,或者request成功请求并且response body用于发送结果数据到客户端时。

从Entity中读取内容,可以通过HttpEntity#getContent()获取input stream,也可以向HttpEntity#writeTo(OutputStream)提供一个output stream,该方法会将内容一次性全部写回给指派的stream。

当消息到达并且Entity已经被接收时,可以用 HttpEntity#getContentType() 和 HttpEntity#getContentLength() 方法读取常用的元数据,如Content-Type 和 Content-Length 头信息,Content-Type头信息包含文本的mime-type编码信息如 text/plain 或者 text/html,该信息可以通过 HttpEntity#getContentType() 获取到,如果Header里面不包含这些信息,那么 HttpEntity#getContentLength()  会返回-1 并且  HttpEntity#getContentType()  返回 NULL,如果Header包含这些信息,那么 HttpEntity#getContentType() 会返回 Header 对象。

当创建一个出站消息的entity时,必须同时指定其相关的参数。

[java]  view plain  copy
  1. StringEntity myEntity = new StringEntity("important message",   
  2.    ContentType.create("text/plain""UTF-8"));  
  3.   
  4. System.out.println(myEntity.getContentType());  
  5. System.out.println(myEntity.getContentLength());  
  6. System.out.println(EntityUtils.toString(myEntity));  
  7. System.out.println(EntityUtils.toByteArray(myEntity).length);  

输出:

[java]  view plain  copy
  1. Content-Type: text/plain; charset=utf-8  
  2. 17  
  3. important message  
  4. 17  

1.1.5 确保释放低级资源

为了保证恰当的释放系统资源,使用完毕后必须关闭entity相关的stream和response。

[java]  view plain  copy
  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. }  

关闭内容流和关闭response的不同之处在于,关闭内容流会尝试通过消费entity的content来保持底层连接,而关闭response则是直接关闭和丢弃掉这个连接。

注意 HttpEntity#writeTo(OutputStream)  方法也需要在entity被写出到流之后恰当的释放系统资源。如果通过HttpEntity#getContent()获取了java.io.InputStream流的实例,那么在finally阶段也需要关闭该流。

当处理streaming entites时,可以使用 EntityUtils#consume(HttpEntity) 方法来确保entity内容已经被完全消费并且底层stream已经被关闭。

有另一种情况是,当我们只需要从response content里面获取一小部分数据,但是消费剩余数据和保持连接复用的性能损失又太高时,我们可以直接关闭response。

[java]  view plain  copy
  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内容

消费Entity内容的推荐方式是使用 HttpEntity#getContent() 或者 HttpEntity#writeTo(OuptputStream) 方法,也可以使用EntityUtils类,该类提供了一些静态方法来简化读取Entity内容或者其他信息。通过使用该类的某些方法,你可以以String /byte[] 方式来获取整个content body,从而替代直接读取 InputStream 的方式。但是,除非你知道response entity来自于可信HTTP server 并且其长度有限,否则不推荐使用EntityUtils。

[java]  view plain  copy
  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不止一次,这时就需要将内容通过内存或者磁盘的方式缓存起来,最简单的实现方式就是使用BufferedHttpEntity包装源Entity,该类会将源 Entity 的内容读取到内存中。如果通过其他方式来实现,那么就必须要保存一个源entity了。

[java]  view plain  copy
  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连接高效的输出内容的类,这些类的实例可以跟POST和PUT等request相关联并且为request封装entity内容,HttpClient提供了最常用的数据容器如string, byte array , input stream 和文件 : StringEntity , ByteArrayEntity,InputStreamEntity,和FileEntity。

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

注意 InputStreamEntity 不可以重复,因为它只能从底层数据流读取一次。通常建议用HttpEntity的实现(self-contained)来替代普通的InputStreamEntity,FileEntity是一个不错的起始点。


1.1.7.1 HTML表单

许多应用程序需要模拟表单提交的过程,比如登录web应用或者提交input 数据,HttpClient提供了UrlEncodedFormEntity类来简化这个过程。

[java]  view plain  copy
  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);  

UrlEncodedFormEntity实例会使用URL encoding来编码参数并且产生出如下内容:

[java]  view plain  copy
  1. param1=value1¶m2=value2  

1.1.7.2 内容分块

通常来说,建议让HttpClient选择最合适的传输编码,HttpClient选择的传输编码会基于被传输的HTTP消息的属性而定。你可以通过设置HttpEntity#setChunked()为true来通知HttpClient需要进行chunk编码。注意HttpClient只是把这一标识当做一个提示使用,该值在不支持chunk编码的HTTP协议版本中如HTTP/1.0中会被忽略。

[java]  view plain  copy
  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

最简单和方便处理response的方式是使用ResponseHandler,该类包含了handleResponse(HttpResponse response)方法。该方法使得用户完全不需要去操心连接管理的事情。当使用ResponseHandler时,HttpClient会自动确保将连接释放回连接管理器,无论request是否执行成功。

[java]  view plain  copy
  1. CloseableHttpClient httpclient = HttpClients.createDefault();  
  2. HttpGet httpget = new HttpGet("http://localhost/json");  
  3.   
  4. ResponseHandler<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. MyJsonObject myjson = client.execute(httpget, rh); 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值