在上文中分析了 HttpURLConnection的用法,功能还是比较简单的,没有什么封装

接下来看看Apache HttpClient是如何封装httpClient的

组成

HttpClient 5 的系统架构主要由以下几个部分组成:

  1. HttpCore:核心包,包含了 HTTP 协议的核心抽象和实现,定义了 HTTP 客户端和服务端的基本组件,例如请求消息、响应消息、传输层等。
  2. HttpClient:高级 API,封装了 HttpCore 包中的核心抽象,提供了一组简单易用的 API,以便于客户端应用程序发送 HTTP 请求。
  3. HttpAsyncClient:异步 API,是基于 HttpCore 和 HttpClient 构建的异步 HTTP 客户端,可以通过异步方式实现 HTTP 请求。
  4. HttpClient 和 HttpAsyncClient 都可以通过扩展进行定制和优化,可以添加拦截器、设置连接管理器、Cookie 管理器、认证器等。

请求代码

GET请求代码

String resultContent = null;
String url = "http://127.0.0.1:8081/get";
HttpGet httpGet = new HttpGet(url);
//通过工厂获取
CloseableHttpClient httpClient = HttpClients.createDefault();
//请求
CloseableHttpResponse response = httpClient.execute(httpGet); // Get status code
System.out.println(response.getVersion());
// HTTP/1.1
System.out.println(response.getCode());
// 200
System.out.println(response.getReasonPhrase());
// OK
HttpEntity entity = response.getEntity();
// Get response information
resultContent = EntityUtils.toString(entity);
System.out.println(resultContent);

代码分析

创建实例

Apache HttpClient提供了一个工厂类来返回HttpClient实例

但实际上都是通过HttpClientBuilder去创建的,

Apache HttpClient通过构建者模式加上策略模式实现非常灵活的配置,以实现各种不同的业务场景

通过看build()的代码,创建HttpClient主要分为两步

    public static CloseableHttpClient createDefault() {
return HttpClientBuilder.create().build();
}

第一步是初始化配置

里边很多策略模式的使用,可以实现相关的类来拓展自己的需求,可以通过HttpClient的set方法把新的策略设置进去,其他配置也可以通过RequestConfig设置好

ConnectionKeepAliveStrategy keepAliveStrategyCopy = this.keepAliveStrategy;
if (keepAliveStrategyCopy == null) {
keepAliveStrategyCopy = DefaultConnectionKeepAliveStrategy.INSTANCE;
}
AuthenticationStrategy targetAuthStrategyCopy = this.targetAuthStrategy;
if (targetAuthStrategyCopy == null) {
targetAuthStrategyCopy = DefaultAuthenticationStrategy.INSTANCE;
}
AuthenticationStrategy proxyAuthStrategyCopy = this.proxyAuthStrategy;
if (proxyAuthStrategyCopy == null) {
proxyAuthStrategyCopy = DefaultAuthenticationStrategy.INSTANCE;
}

在这里会初始化包括连接管理器、请求重试处理器、请求执行器、重定向策略、认证策略、代理、SSL/TLS等配置

第二步是创建处理器链

通过组合多个处理器来构建成处理器链处理请求

需要注意的是这里的添加顺序的方法,添加最终的执行处理器调用的是addLast()

处理器链中的每个处理器都有不同的功能,例如请求预处理、重试、身份验证、请求发送、响应解析等等。在每个处理器的处理过程中,可以对请求或响应进行修改或扩展,以满足不同的需求

最后再把初始化好的参数传递给InternalHttpClient返回一个HttpClient实例

发起请求

接下来看看请求方法

CloseableHttpResponse response = httpClient.execute(httpGet);

主要请求方法在InternalHttpClient#doExecute

主要分为三步,第一步是将各种配置填充到上下文中HttpContext

第二步,执行刚刚封装执行链

//execChain就是上一步封装好的执行链
final ClassicHttpResponse response = this.execChain.execute(ClassicRequestBuilder.copy(request).build(), scope);

执行 execute 方法,执行链中的处理器被依次调用,每个元素都可以执行一些预处理、后处理、重试等逻辑

第三步,请求结束后,将结果转换为CloseableHttpResponse返回

自定义拦截器和处理器

接下来试试加一下自定义的拦截器和处理器

拦截器和处理器的实现是不一样的,处理器的实现是ExecChainHandler,拦截器是HttpResponseInterceptorHttpRequestInterceptor

//执行链处理器
class MyCustomInterceptor implements ExecChainHandler {
@Override
public ClassicHttpResponse execute(ClassicHttpRequest request, ExecChain.Scope scope, ExecChain chain) throws IOException, HttpException {
System.out.println("MyCustomInterceptor-------------");
//调用下一个链
return chain.proceed(request,scope);
}
} //响应拦截器
class MyCustomResponseInterceptor implements HttpResponseInterceptor { @Override
public void process(HttpResponse response, EntityDetails entity, HttpContext context) throws HttpException, IOException {
System.out.println("MyCustomResponseInterceptor-------------");
}
} //请求拦截器
class MyCustomRequestInterceptor implements HttpRequestInterceptor { @Override
public void process(HttpRequest request, EntityDetails entity, HttpContext context) throws HttpException, IOException {
System.out.println("MyCustomRequestInterceptor-------------");
}
}

然后加入到拦截链中,custom()方法返回HttpClientBuilder来支持自定义

CloseableHttpClient httpClient = HttpClients.custom()
.addExecInterceptorLast("myCustomInterceptor", new MyCustomInterceptor())
.addRequestInterceptorFirst(new MyCustomRequestInterceptor())
.addResponseInterceptorLast(new MyCustomResponseInterceptor())
.build();

注意看日志就有输出了

拦截器和处理器都是用于拦截请求和响应的中间件,但它们在功能上有些不同:

  1. 拦截器:在请求发送前或响应返回后对请求或响应进行修改,例如添加、删除、修改请求头或响应头、修改请求体等。拦截器的主要作用是拦截请求和响应,对它们进行一些操作,并将它们传递给下一个拦截器或处理器
  2. 处理器:用于执行实际的请求和响应处理,例如建立连接、发送请求、解析响应等。处理器通常是在整个请求-响应流程中的最后一环,负责将最终的响应结果返回给调用方

总之,拦截器和处理器都是用于处理请求和响应的中间件,但它们的职责和功能略有不同

异步请求

异步请求的HttpAsyncClient通过HttpAsyncClients工厂返回

主要的流程和同步请求差不多,包括初始化配置和初始化执行链,主要差异在执行请求那里

因为是异步执行,需要开启异步请求的执行器线程池,通过httpClient.start();方法来设置异步线程的状态,否则异步请求将无法执行

@Override
public final void start() {
if (status.compareAndSet(Status.READY, Status.RUNNING)) {
executorService.execute(ioReactor::start);
}
}

如果没有开启,会抛出异常

if (!isRunning()) {
throw new CancellationException("Request execution cancelled");
}

因为是异步请求,所以请求方法需要提供回调方法,主要实现三个方法,执行完成、失败和取消

//创建url
SimpleHttpRequest get = SimpleHttpRequest.create("GET", url); Future<SimpleHttpResponse> future = httpClient.execute(get,
//异步回调
new FutureCallback<SimpleHttpResponse>() {
@Override
public void completed(SimpleHttpResponse result) {
System.out.println("completed---------------");
} @Override
public void failed(Exception ex) {
System.out.println("failed---------------");
} @Override
public void cancelled() {
System.out.println("cancelled---------------");
}
}); SimpleHttpResponse response = future.get();

通过future.get()来获取异步结果,接下来看看底层是怎么实现的

//请求
execute(SimpleRequestProducer.create(request), SimpleResponseConsumer.create(), context, callback);

异步请求会创建SimpleRequestProducerSimpleResponseConsumer来处理请求和响应,execute()也支持我们自己传进去

最终的请求和数据的接收都是依赖管道,过程有点像NIO

当请求时,会调用requestProducer.produce(channel);把请求数据写入channel中,在响应时,responseConsumerchannel中取得数据

源码的整一块请求代码都是通过几个匿名函数的写法完成的,看的有点绕

发起请求,匿名函数段代表的是RequestChannel的请求方法

//requestProducer的sendRequest方法
void sendRequest(RequestChannel channel, HttpContext context)

因为RequestChannel类是一个函数式接口,所以可以通过这种方式调用

public interface RequestChannel {
//请求方法也是叫sendRequest
void sendRequest(HttpRequest request, EntityDetails entityDetails, HttpContext context) throws HttpException, IOException;
}

生产和消费数据的代码都在那一刻函数段中,具体的实现细节可以看一下那一段源码,最终requestProducer也是委托RequestChannel来发起请求

消费完后通过一层一层的回调,最终到达最上边自己实现的三个方法上

异步的HttpClient也可以自定义拦截器喝处理器,实现方式和上边的一样,处理异步处理器的实现不同

 CloseableHttpClient httpClient = HttpClients.custom()
.setDefaultRequestConfig(config)
.addExecInterceptorLast("myCustomInterceptor", new MyCustomInterceptor())
.addRequestInterceptorFirst(new MyCustomRequestInterceptor())
.addResponseInterceptorLast(new MyCustomResponseInterceptor())
.build();

使用示例

创建HttpClient

如果是同步的就使用HttpClients工厂,异步的使用HttpAsyncClients

//返回默认的
CloseableHttpClient httpClient = HttpClients.createDefault();

如果想实现自定义配置,可以使用HttpClients.custom()方法

基本的配置被封装在RequestConfig类中

RequestConfig config = RequestConfig.custom()
.setConnectionRequestTimeout(3L, TimeUnit.SECONDS)
.setResponseTimeout(3L, TimeUnit.SECONDS)
.setDefaultKeepAlive(10L , TimeUnit.SECONDS)
.build();

如果还不满足,可以还可以去实现这些策略类

使用自定义配置创建httpClient

CloseableHttpClient httpClient = HttpClients.custom()
.setDefaultRequestConfig(config)
.addExecInterceptorLast("myCustomInterceptor", new MyCustomInterceptor())
.addRequestInterceptorFirst(new MyCustomRequestInterceptor())
.addResponseInterceptorLast(new MyCustomResponseInterceptor())
.build();

GET方法请求

 String url = "http://127.0.0.1:8081/get";

List<NameValuePair> nvps = new ArrayList<>();
// GET 请求参数
nvps.add(new BasicNameValuePair("username", "test"));
nvps.add(new BasicNameValuePair("password", "password"));
//将参数填充道url中
URI uri = new URIBuilder(new URI(url))
.addParameters(nvps)
.build();
//创建get请求对象
HttpGet httpGet = new HttpGet(uri); //发起请求
CloseableHttpResponse response = httpClient.execute(httpGet); // Get status code
System.out.println(response.getVersion()); // HTTP/1.1
System.out.println(response.getCode()); // 200
HttpEntity entity = response.getEntity();
// Get response information
String resultContent = EntityUtils.toString(entity);
System.out.println(resultContent);

POST请求

这次将参数写到HttpEntity

String url = "http://127.0.0.1:8081/post";

List<NameValuePair> nvps = new ArrayList<>();
// GET 请求参数
nvps.add(new BasicNameValuePair("username", "test"));
nvps.add(new BasicNameValuePair("password", "password")); UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nvps, StandardCharsets.UTF_8); HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(formEntity); CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = httpClient.execute(httpPost); // Get status code
System.out.println(response.getVersion()); // HTTP/1.1
System.out.println(response.getCode()); // 200
HttpEntity entity = response.getEntity();
// Get response information
String resultContent = EntityUtils.toString(entity);
System.out.println(resultContent);

和GET请求基本一致,除了用的是HttpPost,参数可以像GET一样填充到url中,也可以使用HttpEntity填充到请求体里

Json请求

String json = "{"
+ " \"username\": \"test\","
+ " \"password\": \"password\""
+ "}";
StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
HttpPost post = new HttpPost("http://127.0.0.1:8081/postJson");
post.setEntity(entity);
CloseableHttpClient client = HttpClients.createDefault();
CloseableHttpResponse response = client.execute(post); // Get status code
System.out.println(response.getCode()); // 200
// Get response information
String resultContent = EntityUtils.toString(response.getEntity());
System.out.println(resultContent);

总结

HttpURLConnection相比,做了很多封装,功能也强大了很多,例如连接池、缓存、重试机制、线程池等等,并且对于请求参数的设置更加灵活,还封装了异步请求、HTTPS等、自定义拦截器和处理器等

请求是使用了处理链的方式发起的,可以对请求和响应进行一系列处理,好处是可以将这些处理器封装成一个公共的类库,然后通过自己组合来满足自己的需求,还可以在请求和响应的不同阶段进行拦截和修改,例如添加请求头、修改请求参数、解密响应数据等,不需要在一个大类里写很多代码了,已免代码臃肿

性能方面使用了连接池技术,可以有效地复用连接,提高性能

不得不说是apache的项目,源码使用了包括构建者模式、策略模式、责任链模式等设计模式对整个httpClient进行了封装,学习到了

Apache HttpClient使用和源码分析的更多相关文章

  1. Quartz学习--二 Hello Quartz! 和源码分析

    Quartz学习--二  Hello Quartz! 和源码分析 三.  Hello Quartz! 我会跟着 第一章 6.2 的图来 进行同步代码编写 简单入门示例: 创建一个新的java普通工程 ...

  2. Android Debuggerd 简要介绍和源码分析(转载)

    转载: http://dylangao.com/2014/05/16/android-debuggerd-%E7%AE%80%E8%A6%81%E4%BB%8B%E7%BB%8D%E5%92%8C%E ...

  3. Java并发编程(七)ConcurrentLinkedQueue的实现原理和源码分析

    相关文章 Java并发编程(一)线程定义.状态和属性 Java并发编程(二)同步 Java并发编程(三)volatile域 Java并发编程(四)Java内存模型 Java并发编程(五)Concurr ...

  4. Kubernetes Job Controller 原理和源码分析(一)

    概述什么是 JobJob 入门示例Job 的 specPod Template并发问题其他属性 概述 Job 是主要的 Kubernetes 原生 Workload 资源之一,是在 Kubernete ...

  5. Kubernetes Job Controller 原理和源码分析(二)

    概述程序入口Job controller 的创建Controller 对象NewController()podControlEventHandlerJob AddFunc DeleteFuncJob ...

  6. Kubernetes Job Controller 原理和源码分析(三)

    概述Job controller 的启动processNextWorkItem()核心调谐逻辑入口 - syncJob()Pod 数量管理 - manageJob()小结 概述 源码版本:kubern ...

  7. OpenMP For Construct dynamic 调度方式实现原理和源码分析

    OpenMP For Construct dynamic 调度方式实现原理和源码分析 前言 在本篇文章当中主要给大家介绍 OpenMp for construct 的实现原理,以及与他相关的动态库函数 ...

  8. jQuery静态方法globalEval使用和源码分析

    Eval函数大家都很熟悉,但是globalEval方法却很少使用,大多数参考手册也没有相关api,下面就对其用法和源码相应介绍: jQuery.globalEval()函数用于全局性地执行一段Java ...

  9. Mybatis的缓存——一级缓存和源码分析

    目录 什么是缓存? 一级缓存 测试一. 测试二. 总结: 一级缓存源码分析: 1. 一级缓存到底是什么? 得出结论: 2. 一级缓存什么时候被创建? 3. 一级缓存的执行流程 结论: 一级缓存源码分析 ...

  10. thrift使用和源码分析

    1 前言 thrift的官方文档比较差,很多细节没有介绍清楚,比如require.optional和default字段的区别是什么,为什么字段前面要写序号等,带着这些疑问,我们需要阅读生成的源码来了解 ...

随机推荐

  1. 线上服务异常的定位、处理与优化的探索 - 第三章 Java虚拟机

    Java虚拟机   之所以引入关于JVM的篇章,是发现多数项目发生的线上问题很大的几率源自JVM调优配置不当引起.对JVM的内存模型.GC垃圾回收机制.调优方式有一个系统化的了解后,可以快速处理或避免 ...

  2. Kafka技术专题之「性能调优篇」消息队列服务端出现内存溢出OOM以及相关性能调优实战分析

    内存问题 本篇文章介绍Kafka处理大文件出现内存溢出 java.lang.OutOfMemoryError: Direct buffer memory,主要内容包括基础应用.实用技巧.原理机制等方面 ...

  3. 一文了解 Dubbo 的代码架构

    整体设计 图例说明: 图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口. 图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层 ...

  4. Vue 响应式原理模拟以及最小版本的 Vue的模拟

    在模拟最小的vue之前,先复习一下,发布订阅模式和观察者模式 对两种模式有了了解之后,对Vue2.0和Vue3.0的数据响应式核心原理 1.Vue2.0和Vue3.0的数据响应式核心原理 (1).  ...

  5. vue项目 h5上拉加载(分页功能)

    <template> <div class="receivable"> <div class="application-header fle ...

  6. CMS可视化---ECharts图表

    一.ECharts介绍 ECharts,全称Enterprise Charts,商业级数据图表,一个纯Javascript的图表库,能够流畅的运行在PC以及移动设备上,兼容当前绝大部分浏览器.为我们许 ...

  7. github的初体验

    首先你得注册一个自己的GitHub账号,注册网址:https://github.com/join有了自己的账号以后,就可以进行登录,开始创建一个新的项目创建一个新的项目,填写项目名称,描述创建完成之后 ...

  8. Typora 最后一个免费版本

    介绍 Typora 是一款轻量级的 Markdown 编辑器,其最为出众的特点是: 所见即所得. Typora 于2021年11月23日推出了第一个正式版,并转为收费.不过价格也算合理,89元/3台设 ...

  9. MySQL 合并查询union 查询出的行合并到一个表中

    在合并查询中,尤其是二分类的情况,在查询结果是相同列名的时候可以考虑合并查询.先查询出行的结果,再使用union或者union all合并查询结果. 另外如果 union 和 order by 一起使 ...

  10. 《爆肝整理》保姆级系列教程-玩转Charles抓包神器教程(5)-Charles如何设置捕获Https会话

    1.简介 在大数据时代,互联网时代,个人信息安全尤为重要,网络安全在近日多起电信诈骗事情发酵下的情况下,引起国家,企业,个人对于互联网安全进一步的重视.而之前很多以http协议传输的网站出现的网站信息 ...