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
  • 2. 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接口实现类需要满足的特性**
  • 3. HTTP execution context
  • 4. HTTP protocol interceptors
  • 5. Exception处理
  • 6. 取消请求与 处理重定向

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包导入工程即可)

  1. 创建HttpClient对象。

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

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

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

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

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

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

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

上述过程使用代码就是:

CloseableHttpClient httpclient = HttpClients.createDefault();

//创建请求对象
HttpGet httpget = new HttpGet("http://localhost/");

//httpclient和server通信,将该请求转交给Server,并返回reposne
CloseableHttpResponse response = httpclient.execute(httpget);
try {
    <...>
} finally {
    response.close();
}

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的那个资源请求的。

例如:

HttpGet httpget = new HttpGet(
     "http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq=");

//或是
URI uri = new URIBuilder()
        .setScheme("http")
        .setHost("www.google.com")
        .setPath("/search")
        .setParameter("q", "httpclient")
        .setParameter("btnG", "Google Search")
        .setParameter("aq", "f")
        .setParameter("oq", "")
        .build();
HttpGet httpget = new HttpGet(uri);

2.2.2 HTTP 响应对象

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

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, 
HttpStatus.SC_OK, "OK");

System.out.println(response.getProtocolVersion());  //HTTP/1.1
System.out.println(response.getStatusLine().getStatusCode());  //200
System.out.println(response.getStatusLine().getReasonPhrase()); //OK
System.out.println(response.getStatusLine().toString()); //HTTP/1.1 200 OK

2.2.3 Header

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

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, 
    HttpStatus.SC_OK, "OK");
response.addHeader("Set-Cookie", 
    "c1=a; path=/; domain=localhost");
response.addHeader("Set-Cookie", 
    "c2=b; path=\"/\", c3=c; domain=\"localhost\"");
    
Header h1 = response.getFirstHeader("Set-Cookie");
System.out.println(h1); //Set-Cookie: c1=a; path=/; domain=localhost
Header h2 = response.getLastHeader("Set-Cookie");
System.out.println(h2);    //Set-Cookie: c2=b; path="/", c3=c; domain="localhost"
Header[] hs = response.getHeaders("Set-Cookie");
System.out.println(hs.length); //2

2.2.4 Http entity

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

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

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

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

  3. 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。例如:
StringEntity myEntity = new StringEntity("important message",  ContentType.create("text/plain", "UTF-8"));

System.out.println(myEntity.getContentType());    //Content-Type: text/plain; charset=utf-8
System.out.println(myEntity.getContentLength()); //17

System.out.println(EntityUtils.toString(myEntity));   //important message

System.out.println(EntityUtils.toByteArray(myEntity).length);  //17

entity使用注意事项

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

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/");
CloseableHttpResponse response = httpclient.execute(httpget);
try {
    HttpEntity entity = response.getEntity();
    if (entity != null) {
        InputStream instream = entity.getContent();
        try {
            // do something useful
        } finally {
            instream.close();  //关闭流,但是底层的connection是alive的
        }
    }
} finally {
    response.close();  // 立即关闭,丢弃connection
}

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】,底层的流会自动关闭)。示例代码如下:

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/");
CloseableHttpResponse response = httpclient.execute(httpget);
try {
    HttpEntity entity = response.getEntity();
    if (entity != null) {
        InputStream instream = entity.getContent();
        int byteOne = instream.read();
        int byteTwo = instream.read();
        // Do not need the rest
        //忽略stream的关闭
    }
} finally {
    response.close();//当response关闭时,关闭connection,关闭stream
}

获取entity的完整内容

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

  1. 通过HttpEntity.getContent()返回的流读取;
  2. 通过HttpEntity.writeTo(OutputStream) 设置的流读取;
  3. 通过EntityUtils 的静态方法(不推荐使用,除非你信任该 entity内容源、且包含的数据量较小,否则不安全且效率慢)
    其中第一、二种方法是推荐的方法。EnityUtils的使用示例如下:
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/");
CloseableHttpResponse response = httpclient.execute(httpget);
try {
    HttpEntity entity = response.getEntity();
    if (entity != null) {
        long len = entity.getContentLength();
        if (len != -1 && len < 2048) {
            System.out.println(EntityUtils.toString(entity));
        } else {
            // Stream content out
        }
    }
} finally {
    response.close();
}

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

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

File file = new File("somefile.txt");
FileEntity entity = new FileEntity(file, 
    ContentType.create("text/plain", "UTF-8"));        

HttpPost httppost = new HttpPost("http://localhost/action.do");
httppost.setEntity(entity);

2.2.5 Response handlers

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

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

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/json");

ResponseHandler rh = new ResponseHandler() {

    @Override
    public JsonObject handleResponse(
            final HttpResponse response) throws IOException {
        StatusLine statusLine = response.getStatusLine();
        HttpEntity entity = response.getEntity();
        if (statusLine.getStatusCode() >= 300) {
            throw new HttpResponseException(
                    statusLine.getStatusCode(),
                    statusLine.getReasonPhrase());
        }
        if (entity == null) {
            throw new ClientProtocolException("Response contains no content");
        }
        Gson gson = new GsonBuilder().create();
        ContentType contentType = ContentType.getOrDefault(entity);
        Charset charset = contentType.getCharset();
        Reader reader = new InputStreamReader(entity.getContent(), charset);
        return gson.fromJson(reader, MyJsonObject.class);
    }
};


MyJsonObject myjson = client.execute(httpget, rh);

2.2.6 HttpClient接口

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

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

ConnectionKeepAliveStrategy keepAliveStrat = new DefaultConnectionKeepAliveStrategy() {

    @Override
    public long getKeepAliveDuration(
            HttpResponse response,
            HttpContext context) {
        long keepAlive = super.getKeepAliveDuration(response, context);
        if (keepAlive == -1) {
            // Keep connections alive 5 seconds if a keep-alive value
            // has not be explicitly set by the server
            keepAlive = 5000;
        }
        return keepAlive;
    }

};

CloseableHttpClient httpclient = HttpClients.custom()
        .setKeepAliveStrategy(keepAliveStrat)
        .build();

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

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

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

CloseableHttpClient httpclient = HttpClients.createDefault();
try {
    <...>
} finally {
    httpclient.close();
}

3. HTTP execution context

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

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

HttpContext context = <...>
HttpClientContext clientContext = HttpClientContext.adapt(context);
HttpHost target = clientContext.getTargetHost();
HttpRequest request = clientContext.getRequest();
HttpResponse response = clientContext.getResponse();
RequestConfig config = clientContext.getRequestConfig();

4. HTTP protocol interceptors

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

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

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

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

CloseableHttpClient httpclient = HttpClients.custom()
        .addInterceptorLast(new HttpRequestInterceptor() {

            public void process(
                    final HttpRequest request,
                    final HttpContext context) throws HttpException, IOException {
                AtomicInteger count = (AtomicInteger) context.getAttribute("count");
                request.addHeader("Count", Integer.toString(count.getAndIncrement()));
            }

        })
        .build();

AtomicInteger count = new AtomicInteger(1);
HttpClientContext localContext = HttpClientContext.create();
localContext.setAttribute("count", count);

HttpGet httpget = new HttpGet("http://localhost/");
for (int i = 0; i < 10; i++) {
    CloseableHttpResponse response = httpclient.execute(httpget, localContext);
    try {
        HttpEntity entity = response.getEntity();
    } finally {
        response.close();
    }
}

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规范禁止的需要用户交互的重定向之外)。用户自定义自己的重定向处理策略,来处理请求中的重定向;

LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();
CloseableHttpClient httpclient = HttpClients.custom()
        .setRedirectStrategy(redirectStrategy)
        .build();

你可能感兴趣的:(JAVA)