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

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

运用的版别

      <dependency>
            <groupId>org.apache.httpcomponents.client5</groupId>
            <artifactId>httpclient5</artifactId>
            <version>5.2.1</version>
        </dependency>

组成

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等装备

第二步是创立处理器链

经过组合多个处理器来构建成处理器链处理恳求

Apache HttpClient运用和源码剖析

需求注意的是这里的增加顺序的办法,增加终究的履行处理器调用的是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的恳求办法

Apache HttpClient运用和源码剖析

//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来建议恳求

Apache HttpClient运用和源码剖析

消费完后经过一层一层的回调,终究抵达最上边自己完结的三个办法上

异步的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();

假如还不满意,能够还能够去完结这些战略类

Apache HttpClient运用和源码剖析

运用自定义装备创立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进行了封装,学习到了