• HttpClient以及连接池的使用


    一、开始使用httpclient

    本文档需要使用的依赖有如下几个:

    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
      <version>4.5.10</version>
    </dependency>
    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpmime</artifactId>
      <version>4.5.10</version>
    </dependency>
    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpcore</artifactId>
      <version>4.4.11</version>
    </dependency>

    1、创建HttpClient对象。

    创建httpclient对象的方式有两种,

    第一种是使用默认配置创建:

    CloseableHttpClient client = HttpClients.createDefault();

    第二种是使用HttpClients.custom()定制:

    HttpClients.custom().setDefaultRequestConfig(RequestConfig)

    这其中RequestConfig的设计方式可以学习一下。RequestConfig只有一个默认访问修饰符的构造函,这就意味着我们在使用这个类的时候不能之间构建示例;RequestConfig有很多私有属性,这些属性没有get/set方法,所以,即便是我们拿到了RequestConfig类的实例,也不能去修改其中的属性值。那么RequestConfig是如何构造实例并且为属性赋值的呢?RequestConfig在实例化的时候会利用其公共内部类Builder。使用方式为RequestConfig.custom().setXXX().builder()。调用custom方法返回一个Builder类实例,改类里有设置RequestConfig同名属性的各种方法,设置完毕后调用builder方法构造一个新的RequestConfig实例。从这个过程中可以看出来,RequestConfig对象的属性是不可变的。

    有几个参数我们自己必须设置一下:

    (1)connectionRequestTimeout:从连接池中获取连接的超时时间,超过该时间未拿到可用连接,会抛出:
    ConnectionPoolTimeoutException: Timeout waiting for connection from pool

    (2)connectTimeout:连接上服务器(握手成功)的时间,超出该时间抛出connect timeout

    (3)socketTimeout:服务器返回数据(response)的时间,超过该时间抛出read timeout

    以上3个超时相关的参数如果未配置,默认为-1,意味着无限大,就是一直阻塞等待。

    2、创建请求方法的实例

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

    HttpGet httpGet = new HttpGet("https://www.baidu.com");

    3、组装请求参数

    对于get请求,可以直接在构建HttpGet对象的时候在url中加入,也可以使用URIBuilder构建参数,具体可以参考下面的两种方式:

    1 URIBuilder builder = new URIBuilder("http://example.com/");
    2 builder.setParameter("var1", "value1").setParameter("var2", "value2");
    3 HttpGet request = new HttpGet(builder.build());

    或使用了建造者模式的URIBuilder

    URI uri = new URIBuilder()
                .setScheme("http")
                .setHost("www.example.cn")
                .setPath("/search")
                .setParameter("var1", "value1")
                .build();

    对于HttpPost对象而言,可调用setEntity(HttpEntity entity)方法来设置请求参数:

    //拼接参数
    List<NameValuePair> params= new ArrayList<NameValuePair>();
    params.add(new BasicNameValuePair("username", "vip"));
    params.add(new BasicNameValuePair("password", "secret"));
    httpPost.setEntity(new UrlEncodedFormEntity(params));

    其中setEntity的参数种类可以有很多种,比如:

    JSONObject params = new JSONObject();
    params.put("var1", "value1");
    params.put("var2", "value2");
    httpPost.setEntity(new StringEntity(params.toJSONString(), "utf-8"));

    或者使用mutltipar形式传输参数:

    InputStream in = httpResponse.getEntity().getContent();
    InputStreamBody inputStreamBody = new InputStreamBody(in, "filename");
    HttpEntity entity = MultipartEntityBuilder.create()
                .addPart("image", inputStreamBody)
                .addPart("sign", new StringBody("", ContentType.APPLICATION_XML))
                .build();

    使用MultipartEntityBuilder需要依赖httpmime包。其中的inputStreamBody是从http查询返回中获得的一个图片的输入流。

    4、发送请求

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

    CloseableHttpResponse httpResponse = httpClient.execute(httpPost);

    5、获取返回内容

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

    // 获取响应状态码
    int statusCode = httpResponse.getStatusLine().getStatusCode();
    // 获取返回内容
    HttpEntity httpEntity = httpResponse.getEntity();
    String content = EntityUtils.toString(httpEntity);

    6、释放连接。

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

    httpClient.close();

    二、http连接池

    1、 为什么使用连接池

    1. 降低延迟:如果不采用连接池,每次连接发起Http请求的时候都会重新建立TCP连接(经历3次握手),用完就会关闭连接(4次挥手),如果采用连接池则减少了这部分时间损耗
    2. 支持更大的并发:如果不采用连接池,每次连接都会打开一个端口,在大并发的情况下系统的端口资源很快就会被用完,导致无法建立新的连接

    2、 创建连接池

    PoolingHttpClientConnectionManager cm = null;
    LayeredConnectionSocketFactory sslsf = null;
    try {
        sslsf = new SSLConnectionSocketFactory(SSLContext.getDefault());
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    
    Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
            .register("https", sslsf)
            .register("http", new PlainConnectionSocketFactory())
            .build();
    
    cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
    cm.setMaxTotal(200);
    cm.setDefaultMaxPerRoute(20);
    
    CloseableHttpClient httpClient = HttpClients.custom()
            .setConnectionManager(cm)
            .build();

    三、注意事项

    1、 错误关闭连接

    连接池中连接都是在发起请求的时候建立,并且都是长连接。Client.java[附录]中的in.close()作用就是将用完的连接释放,下次请求可以复用,这里特别注意的是,如果不使用in.close()而仅仅使用response.close(),结果就是连接会被关闭,并且不能被复用,这样就失去了采用连接池的意义。
    连接池释放连接的时候,并不会直接对TCP连接的状态有任何改变,只是维护了两个Set,leased和avaliabled,leased代表被占用的连接集合,avaliabled代表可用的连接的集合,释放连接的时候仅仅是将连接从leased中remove掉了,并把连接放到avaliabled集合中。

    2、 未关闭连接

    try{
    
    HttpClient httpClient = HttpClientBuilder.create().build();
        HttpPost request = new HttpPost(httpUrl);
        StringEntity params = new StringEntity(new ObjectMapper().writeValueAsString(body), "UTF-8");
        request.addHeader("content-type", "application/json");
        request.setEntity(params);
        httpClient.execute(request); //问题代码
    } catch (Exception ex) {
        logger.error("Http post error. body={}", body, ex);
    }

    上段代码中,httpClient.execute()执行的返回值response没有被close,导致线程一直在等待。可以改用CloseableHttpClient,并保证httpClient.execute()执行的response最终被close掉。而httpClient无需close,可以重复使用。如果使用CloseableHttpResponse,则response的close也不用显示调用。

    CloseableHttpClient httpClient = HttpClientBuilder.create().build();
    HttpPost request = new HttpPost(httpUrl);
    StringEntity params = new StringEntity(new ObjectMapper().writeValueAsString(body), "UTF-8");
    request.addHeader("content-type", "application/json");
    request.setEntity(params);
    //httpClient.execute(request);
    try {
        CloseableHttpResponse response = httpClient.execute(request);
        //response.close()会被自动调用
    } catch (Exception ex) {
        logger.error("Http post execute error. body={}", body, ex);
    }

    四、附录

    1、client.java

    public class Client {
        HttpConnectionManager connManager;
    
        public <T> T get(String path,Class<T> clazz){
            CloseableHttpClient httpClient=connManager.getHttpClient();
            HttpGet httpget = new HttpGet(path);
            String json=null;
            CloseableHttpResponse response=null;
            try {
                response = httpClient.execute(httpget);
                InputStream in=response.getEntity().getContent();
                json=IOUtils.toString(in);
                in.close();
            } catch (UnsupportedOperationException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if(response!=null){
                    try {
                        response.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return JSON.parseObject(json, clazz);
        }
    }

    参考:

    HttpClient中文网.http://www.httpclient.cn/

  • 相关阅读:
    IOC
    软件问题
    POJO和JavaBean
    tail命令
    实现质数遍历并输出所需时间
    完数
    break、continue
    *各种形状
    for、while、do-while
    jenkins实现maven项目自动化部署tomcat
  • 原文地址:https://www.cnblogs.com/zhangcaiwang/p/14251588.html
Copyright © 2020-2023  润新知