HttpClient 使用介绍

之前没有认真了解过 HttpClient 如何实现,现在有时间来整理一下使用及中间步骤介绍
整体分为 5 步:

  1. 创建 HttpClient 对象
  2. 创建 请求 request 对象
  3. 执行请求
  4. 响应分析
  5. 资源释放
package com.maodou.autotest.utils;

import com.maodou.autotest.Application;
import org.apache.http.*;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.boot.SpringApplication;
import sun.misc.ObjectInputFilter;

import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

public class HttpClientUtil {

    /**
     * 总的connection数量
     */
    private final static Integer MAX_CONNECTION = 20;
    /**
     * 每个route允许最多connection数量
     */
    private final static Integer MAX_PRE_ROUTE = MAX_CONNECTION;
    /**
     * 从连接池中取连接的超时时间(若从连接池中没有拿到可用的连接,则 request 会被阻塞,等待时间超过连接超时时间时
     * 则抛出ConnectionPoolTimeoutException异常)
     */
    private final static Integer CON_RST_TIME_OUT = 12000;
    /**
     * 连接超时时间(取得连接之后,接通目标url的连接等待时间,发生超时时会抛出,ConnectionTimeoutException异常)
     */
    private final static Integer CON_TIME_OUT = 12000;
    /**
     * 请求超时时间(连接服务器之后,从服务器获得请求数据的等待时间,即连接上一个 url 之后获取 response 之间的等待时间,若发生超时,则抛出 SocketTimeoutException 异常)
     */
    private final static Integer SOCKET_TIME_OUT = 12000;

    public static String doGet() throws Exception {

        String resultString = "";
        CloseableHttpResponse response = null;
        //*********  一、创建 HttpClient 对象  ***********


        //A、HttpClient 路由计算策略 HttpRoutePlanner
        /*
         * HttpClient既可以直接、又可以通过多个中转路由(hops)和目标服务器建立连接,
         * 在HttpClient中,一个Route指运行环境机器->目标机器host的一条线路,也就是如果目标url的host是同一个,那么它们的route也是一样的。
         * HttpRoutePlanner接口可以用来表示基于http上下文情况下,客户端到服务器的路由计算策略,它有两个实现类:
         * SystemDefaultRoutePlanner这个类基于java.net.ProxySelector,它默认使用jvm的代理配置信息,这个配置信息一般来自系统配置或者浏览器配置。
         * DefaultProxyRoutePlanner这个类既不使用java本身的配置,也不使用系统或者浏览器的配置。它通常通过默认代理来计算路由信息。
         */

        /*
         * B 、HttpClient 连接管理器: HttpCLientConnectionManager
         * 它负责新的 Http 连接的创建、管理连接的生命周期,保证一个http 连接在某个时刻只能被一个线程使用,它有两个实现类:
         * 1.BasicHttpClientConnectionManager
         * 每次只管理一个connection。不过,虽然它是thread-safe的,但由于它只管理一个连接,所以只能被一个线程使用。
         * 每次新来一个请求,如果是相同 route,则会复用旧的连接,但是如果 route 不相同,则会断开旧的连接创建新的连接
         * 2.PoolingHttpClientConnectionManager
         * 它管理着一个连接池,如果每次来一个新的请求,它会先在连接池查找可用连接,如过存在 route 相同并且可用的连接的话,连接池就会直接复用该连接
         * 如果没有找到 route 相同的可用连接时,连接池就会重新创建一个新的连接,如果连接池已经满了的话,就会等待其他连接断开或者直到超时
         */

        /*
         *多线程执行
         * HttpClient已经实现了线程安全。所以我们在实例话 httpClient 时,也要支持为多个请求使用,选择 PoolingHttpClientConnectionManager
         */
        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();

        //默认不对HttpClientBuilder进行配置的话,new出来的CloeableHttpClient实例使用的是PoolingHttpClientConnectionManager。
        //这种情况下HttpClientBuilder创建出的HttpClient实例就可以被多个连接和多个线程共用,在应用容器起来的时候实例化一次,在整个应用结束的时候再调用httpClient.close()就行了。
        //如果没有显式设置,默认每个route只允许最多2个connection,总的connection数量不超过20
        //如果所有的连接请求都是到同一个url,那可以把MaxPerRoute的值设置成和MaxTotal一致,这样就能更高效地复用连接。
        //HttpClient设置最大连接数和每个route的最大连接数示例

        poolingHttpClientConnectionManager.setMaxTotal(MAX_CONNECTION);
        poolingHttpClientConnectionManager.setDefaultMaxPerRoute(MAX_PRE_ROUTE);

        //C、HttpConfig  表示http request的配置信息
        //设置超时时间,如果不设置的话,默认这三个超时时间都为0,也就意味着会无限等待
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(CON_RST_TIME_OUT)
                .setConnectTimeout(CON_TIME_OUT)
                .setSocketTimeout(SOCKET_TIME_OUT).build();

        CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).build();

        try {


            //**********  二、创建 request 对象 *********

            /*
             * GET、HEAD、POST、PUT、DELETE、TRACE和OPTIONS。对使用的类为HttpGet、HttpHead、HttpPost、HttpPut、HttpDelete、HttpTrace和HttpOptions
             * Request的对象建立很简单,一般用目标url来构造就好了
             * 一个Request还可以addHeader、setEntity、setConfig等
             */

            //HttpClient提供URIBuilder工具类来简化URIs的创建和修改过程,下列等同于  HttpGet request = new HttpGet("https://wenku.baidu.com/search/interface/getrelatequery?word=%E4%BD%A0%E5%A5%BD");

            URI uri = new URIBuilder()
                    .setScheme("http")
                    .setHost("wenku.baidu.com")
                    .setPath("/search/interface/getrelatequery")
                    .setParameter("word", "%E4%BD%A0%E5%A5%BD")
                    .build();
            HttpGet request = new HttpGet(uri);

            //通过 setConfig 设置相关请求配置信息
            request.setConfig(requestConfig);
            //通过 addHeader 设置请求头
            request.addHeader("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)");
            //通过 setEntity 设置请求体,见 doPost 请求


            //*********** 三、执行请求 ***********
            /*
             * 最简单的使用方法是调用execute(final HttpUriRequest request)* 整个 execute 执行的常规流程为:
             *
             * new一个 http context
             * 取出 Request 和URL
             * 根据 HttpRoute 的配置看是否需要重写URL
             * 根据 URL 的host、port和scheme设置target
             * 在发送前用 http 协议拦截器处理 request 的各个部分
             * 取得验证状态、user token来验证身份
             * 从连接池中取一个可用的连接
             * 根据request的各种配置参数以及取得的connection构造一个connManaged
             * 打开managed的connection(包括创建route、dns解析、绑定socket、socket连接等)
             * 请求数据(包括发送请求和接收response两个阶段)
             * 查看keepAlive策略,判断连接是否要复用,并设置相应标识
             * 返回response
             * 用http协议拦截器处理response的各个部分
             */

            /*
             * a、Http 上下文 HttpContext
             * HttpClient允许http连接在特定的Http上下文中执行,HttpContext是跟一个连接相关联的,所以它也只能属于一个线程,
             * 如果没有特别设定,在execute的过程中,HttpClient会自动为每一个connectionnew一个HttpClientHttpContext。
             * HttpContext可以包含任意类型的对象,因此如果在多线程中共享上下文会不安全。推荐每个线程都只包含自己的http上下文
             * 在Http请求执行的过程中,HttpClient会自动添加下面的属性到Http上下文中:
             *
             * HttpConnection的实例,表示客户端与服务器之间的连接
             * HttpHost的实例,表示要连接的木包服务器
             * HttpRoute的实例,表示全部的连接路由
             * HttpRequest的实例,表示Http请求。在执行上下文中,最终的HttpRequest对象会代表http消息的状态。Http/1.0和Http/1.1都默认使用相对的uri。但是如果使用了非隧道模式的代理服务器,就会使用绝对路径的uri。
             * HttpResponse的实例,表示Http响应
             * java.lang.Boolean对象,表示是否请求被成功的发送给目标服务器
             * RequestConfig对象,表示http request的配置信息
             * java.util.List<Uri>对象,表示Http响应中的所有重定向地址
             * 我们可以使用HttpClientContext这个适配器来简化和上下文交互的过程。
             */
            response = httpClient.execute(request);


            //**********  四、响应处理 ***********
            /*
             * HttpReaponse是将服务端发回的Http响应解析后的对象。CloseableHttpClient的execute方法返回的response都是CloseableHttpResponse类型
             * getAllHeaders()、getEntity、getRequestLine等,一般这几个方法比较常用
             */
            response.getAllHeaders();
            StatusLine statusLine = response.getStatusLine();
            int status = statusLine.getStatusCode();
            if (status == 200) {
                /**
                 * 对于entity的处理需要特别注意一下。一般来说一个response中的entity只能被使用一次,它是一个流,这个流被处理完就不再存在了。
                 * 先response.getEntity()再使用HttpEntity#getContent()来得到一个java.io.InputStream,然后再对内容进行相应的处理
                 */
                HttpEntity responseEntity = response.getEntity();
                resultString = EntityUtils.toString(responseEntity);

                System.out.println(resultString);
            }

            /*
            有一点非常重要,想要复用一个connection就必须要让它占有的系统资源得到正确释放。释放资源有两种方法:
            1)关闭和entity相关的content stream
            如果使用的是 outputStream  就要保证整个 entity 被 Write Out ,
            如果是 inputStream 就要调用inputStream.close(),或者使用 EntityUtils.consume(entity)保证整个 entity 被消耗掉,
            entity.toString() 方法也可以实现,也会自动把inputStream close掉的,如上代码(只有确保 entity 不是特别大的情况下才可以)

            2)关闭 response
            执行response.close()虽然会正确释放掉该connection占用的所有资源,但是这是一种比较暴力的方式,采用这种方式之后,这个connection就不能被重复使用了。

            关闭stream和关闭response的区别在于前者会尝试保持底层的连接alive,而后者会直接shut down并且丢弃connection
             */
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (response != null) {
                    response.close();
                }
                //********** 五、关闭HttpClient **************
                /*
                调用httpClient.close()会先shut down connection manager,然后再释放该HttpClient所占用的所有资源,关闭所有在使用或者空闲的connection包括底层socket。
                由于这里把它所使用的connection manager关闭了,所以在下次还要进行http请求的时候,要重新new一个connection manager来build一个HttpClient
                 */
                httpClient.close();
                return resultString;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return resultString;
    }

    public static String doPost() throws Exception {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        String resultString = "";
        try{
            //通过 setEntity 设置请求体
            List<NameValuePair> formparams = new ArrayList<NameValuePair>();
            formparams.add(new BasicNameValuePair("url", "/search"));
            UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, Consts.UTF_8);
            HttpPost httppost = new HttpPost("https://wenku.baidu.com/message/getnotice");
            httppost.setEntity(entity);

            response = httpClient.execute(httppost);
            int status = response.getStatusLine().getStatusCode();
            if(status == 200){
                resultString = EntityUtils.toString(response.getEntity());
                System.out.println(resultString);
                return resultString;
            }
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            try{
                if(null !=response ){
                    response.close();
                }
                httpClient.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }

        return resultString;

    }

    /**
     * 当一个CloseableHttpClient的实例不再被使用,并且它的作用范围即将失效,和它相关的连接必须被关闭,关闭方法可以调用CloseableHttpClient的close()方法
     */

    public static void main(String[] args) throws  Exception{
        HttpClientUtil.doGet();
        // {"status":{"code":0,"msg":null},"data":{"relateQuery":["\u767e\u5206\u53f7\u4e0e\u5343\u5206\u53f7\u6362\u7b97","c\u8bed\u8a00\u5982\u4f55\u6253\u5370\u767e\u5206\u53f7","origin\u4e2d\u767e\u5206\u53f7\u8bbe\u7f6e","\u767e\u5206\u53f7\u600e\u4e48\u5199\u89c4\u8303","\u8ba1\u7b97\u5668\u767e\u5206\u53f7\u600e\u4e48\u6309"]}}
        HttpClientUtil.doPost();
        // {"status":{"code":0,"msg":"get notice success"},"data":{"system":[],"diy":[],"advert":[],"errstr":"get notice success"}}
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值