apache httpclient速成

目录标题

导入依赖

c 复制代码
     <dependency>
            <groupId>org.apache.httpcomponents.client5</groupId>
            <artifactId>httpclient5</artifactId>
            <version>5.2.1</version>
        </dependency>

快速使用

不关注其他,只需要发起请求,然后收到即可:

c 复制代码
        CloseableHttpClient httpClient = HttpClients.createDefault(); // 创建client
        
        HttpPost request = new HttpPost("https://www.baidu.com/"); // 请求信息
        request.setHeader("User-Agent",
                "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36");
        RequestConfig config =
                RequestConfig.custom().setResponseTimeout(5000, TimeUnit.MILLISECONDS)
                        .build();
        request.setConfig(config); // 设置连接超时等参数
        
        // 发起调用
        String response = httpClient.execute(request, new HttpClientResponseHandler<String>() {
            @Override
            public String handleResponse(ClassicHttpResponse classicHttpResponse) throws HttpException, IOException {
                System.out.println(classicHttpResponse.getCode());
                HttpEntity entity = classicHttpResponse.getEntity();
                InputStream content = entity.getContent();
                return EntityUtils.toString(entity);

            }
        });

        System.out.println(response);

连接池

http的连接池有什么用?

主要好处在于连接复用,减少创建/销毁 tcp 连接的开销(因为三次握手和四次挥手)。

需要注意的是:

1.http连接池不是万能的,过多的长连接会占用服务器资源,导致其他服务受阻

2.http连接池只适用于请求是经常访问同一主机(或同一个接口)的情况下

3.并发数不高的情况下资源利用率低下

4.httpclient是一个线程安全的类,没有必要由每个线程在每次使用时创建,全局保留一个即可。

比如:

c 复制代码
    PoolingHttpClientConnectionManager poopManager = new PoolingHttpClientConnectionManager();
        poopManager.setMaxTotal(5); // 设置连接池最大连接数
        poopManager.setDefaultConnectionConfig( // 为所有路由设置连接参数
                ConnectionConfig.custom().setConnectTimeout(1000, TimeUnit.MILLISECONDS)
                .setSocketTimeout(1000, TimeUnit.MILLISECONDS).build());
        poopManager.setMaxPerRoute( // 单独为一个路由设置最大连接数
                new HttpRoute(new HttpHost(InetAddress.getByName("www.baidu.com"), 443)),
                1);

        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(poopManager).build();

参数

用如下代码作为示例。设置了连接池最大连接数是20,每个路由最大连接是2。

代码一共有四个路由,每一个网站都用一个异步线程调用10次

c 复制代码
package com.example.springbootproject.httpclient;

import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.pool.PoolStats;

import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class HttpUtil {

    public static final CloseableHttpClient httpClient;
    public static final PoolingHttpClientConnectionManager poolManager;

    static {
        poolManager = new PoolingHttpClientConnectionManager();
        poolManager.setDefaultMaxPerRoute(2);
        poolManager.setMaxTotal(20);
        httpClient = HttpClients.custom()
                // 设置连接池管理
                .setConnectionManager(poolManager)
                .build();
    }


    public static void main(String[] args) {
        HttpUtil.execute("http://www.baidu.com");
        HttpUtil.execute("https://juejin.cn/");
        HttpUtil.execute("https://www.zhihu.com/hot");
        HttpUtil.execute("https://www.bilibili.com/?utm_source=gold_browser_extension");
        // 创建一个定时线程池,包含单个线程
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        // 安排任务在初始延迟后执行,然后每隔一定时间打印状态
        executor.scheduleAtFixedRate(() -> {
            HttpUtil.httpPoolStats();
        }, 0, 1, TimeUnit.SECONDS);
    }

    public static void execute(String url) {
        for (int i = 0; i < 10; i++) {
            ExecutorService executor = Executors.newSingleThreadExecutor();
            executor.submit(() -> {
                HttpGet get = new HttpGet(url);
                CloseableHttpResponse response = null;
                try {
                    Thread.sleep(1000);
                    String execute = HttpUtil.httpClient.execute(get, new HttpClientResponseHandler<String>() {
                        @Override
                        public String handleResponse(ClassicHttpResponse response) throws HttpException, IOException {
                            return EntityUtils.toString(response.getEntity());
                        }
                    });
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            });
        }
    }

    public static void httpPoolStats() {
        // 获取所有路由的连接池状态
        PoolStats totalStats = poolManager.getTotalStats();
        System.out.println("Total status:" + totalStats.toString());
    }
}

连接池状态

PoolStats totalStats = poolManager.getTotalStats();打印出来是这样的[leased: 0; pending: 0; available: 0; max: 20]

  • leased:连接池正在使用的连接(Gets the number of persistent connections tracked by the connection manager currently being used to execute requests.The total number of connections in the pool is equal to {@code available} plus {@code leased})
  • pending:等待空闲连接的数量(Gets the number of connection requests being blocked awaiting a free connection. This can happen only if there are more worker threads contending for fewer connections.)
  • available:当前线程池空闲的连接数量(Gets the number idle persistent connections.)
  • max:最大允许的容量

    可以看到,leased +available = 池子中的连接数。因为一共四个路由,每个路由最大可以有两个连接,所以一共是8个可用连接数。其中pending表示等待空闲的连接数量。

清除闲置连接evictIdleConnections

关闭闲置2s的连接
HttpClients.custom().evictIdleConnections(TimeValue.ofMilliseconds(2000))

或者自己开一个线程调用poolManager.closeIdle(TimeValue.ofMilliseconds(2000));

最终如下

删除过期连接 timeToLive 和evictExpiredConnections

timeToLive 的定义:为了复用连接,一般服务器会返回keep alive时间,这样双方就用一个连接发送多个数据。如果太长,就会产生没用的连接,所以需要关闭。

Defines the total span of time connections can be kept alive or execute requests.

timeToLive 推荐不设置值,使用默认即可。同时打开evictExpiredConnections,

这样会使用服务器返回的keepalive,只要在keepalive时间范围内,连接就不会关闭。


设置timetolive值
poolManager.setDefaultConnectionConfig( ConnectionConfig.custom().setTimeToLive(1000, TimeUnit.MILLISECONDS).build());

和打开删除过期的连接HttpClients.custom().evictExpiredConnections()

设置了之后,发现总会存在pending的连接:

发现是在Closing expired connections时报错,有个方法找不到。

再引入core包就可以了。

c 复制代码
        <dependency>
            <groupId>org.apache.httpcomponents.core5</groupId>
            <artifactId>httpcore5</artifactId>
            <version>5.2.4</version>
        </dependency>

注意释放内存

关闭流

代码中有写到,接response转为字符串返回。其实这个entity.getContent()是一个流,需要被关闭。

c 复制代码
  HttpEntity entity = classicHttpResponse.getEntity();
  String s = EntityUtils.toString(entity);

如何关闭?

  1. 手动关闭使用 EntityUtils.consume(entity);
  2. 实现一个HttpClientResponseHandler,就像我们上面中的那样。底层实现里面就已经调用过了。

http和netty的关系

http是协议,netty是java的一个NIO的编程框架。比如使用netty我们搭建一个高性能的http 服务器,也可以用netty自定义协议通信。

参考:https://juejin.cn/post/7292029688998068243

相关推荐
小时前端3 天前
HTTPS 页面加载 HTTP 脚本被拦?同源代理来救场
前端·https
james的分享8 天前
大数据领域核心 SQL 优化框架Apache Calcite介绍
大数据·sql·apache·calcite
莫寒清8 天前
Apache Tika
java·人工智能·spring·apache·知识图谱
赤月奇8 天前
https改为http
数据挖掘·https·ssl
归叶再无青8 天前
web服务安装部署、性能升级等(Apache、Nginx)
运维·前端·nginx·云原生·apache·bash
忙碌5449 天前
OpenTelemetry实战指南:构建云原生全链路可观测性体系
ios·flink·apache·iphone
悠闲蜗牛�9 天前
Apache Flink实时计算实战指南:从流处理到数据湖仓一体的架构演进
架构·flink·apache
2301_8169978810 天前
Apache Commons工具类
apache
心雨⁢⁢⁣10 天前
RocketMq(Apache RocketMQ 5.2.1-SNAPSHOT)消息消费流程
apache·rocketmq·java-rocketmq
Boxsc_midnight10 天前
【windows电脑浏览器直接访问虚拟机或云端openclaw的方法】一个不需要HTTPS的安全连接通道(基于SSH)
windows·安全·https·openclaw