Okhttp实现原理

OkHttp 是一个高效的 HTTP 客户端库,广泛应用于 Android 和 Java 应用中。它提供了简洁的 API,支持多种协议,如 HTTP/1.x 和 HTTP/2,并且内置了缓存和重试机制。下面是结合源码分析的 OkHttp 的实现原理:

核心组件

OkHttp 的核心组件包括:

  • Call:这是 OkHttp 的主要接口,用于发起 HTTP 请求。一个 Call 对象代表一个 HTTP 请求。
  • Request:包含请求的所有信息,如 URL、HTTP 方法、请求头和请求体。
  • Response:包含服务器响应的所有信息,如状态码、响应头和响应体。
  • Dispatcher:管理 OkHttp 的并发请求和线程池。
  • ConnectionPool:管理 HTTP 连接的复用和释放。
  • Interceptor:提供拦截器链,允许在请求发送前和响应接收后进行修改。

请求的发起和执行

当你调用 OkHttpClientnewCall() 方法创建一个 Call 对象,并调用 execute()enqueue() 方法时,请求的执行流程如下:

  1. 创建 Call 对象Call 对象被创建,它持有 Request 对象的引用,并通过 execute() 同步执行请求或通过 enqueue() 异步执行请求。

  2. 分发请求Call 对象会调用 Dispatcher 来管理请求的执行。如果使用 enqueue()Dispatcher 会在后台线程中执行请求。

  3. 拦截器链:请求经过一系列拦截器的处理。OkHttp 的拦截器按顺序执行,每个拦截器都可以修改请求或响应。拦截器链的最后一个拦截器是 RealInterceptorChain,它负责实际的网络请求。

  4. 建立连接:如果需要,OkHttp 会建立到服务器的 TCP 连接。它会优先使用连接池中的现有连接,如果没有合适的连接,则创建新的连接。

  5. 发送请求:将请求写入到套接字中,等待服务器响应。

  6. 读取响应:读取服务器的响应,解析状态码、响应头和响应体。

  7. 关闭连接:如果不需要保留连接,则关闭连接。否则,连接会被放回连接池。

  8. 处理响应:响应被传递给请求的回调方法或同步返回。

源码分析

Call 的执行流程为例,看看 OkHttp 的执行流程:

Java
1// OkHttpClient.java
2public Response execute(Request request) throws IOException {
3    synchronized (this) {
4        if (closed) throw new IllegalStateException("closed");
5    }
6
7    // Create a new call.
8    Call call = new RealCall(this, request);
9    return call.execute();
10}
11
12// RealCall.java
13public Response execute() throws IOException {
14    // ...
15    RealConnection connection = getConnection();
16    // ...
17
18    // Execute the request.
19    long startNs = System.nanoTime();
20    try {
21        StreamAllocation streamAllocation = connection.newStream(false /* doNotRetry */, false /* isMultiplexed */);
22        // ...
23        return readResponse(streamAllocation, responseBuilder);
24    } finally {
25        streamAllocation.finishedReadingResponseBody();
26        // ...
27    }
28}

在这段代码中,OkHttpClientexecute() 方法创建了一个 RealCall 实例,然后调用 RealCallexecute() 方法来执行请求。RealCallexecute() 方法会获取或创建一个 RealConnection,然后通过这个连接发送请求,并读取响应。

拦截器(Interceptors)

拦截器是 OkHttp 中非常重要的概念,它允许你在请求发送前和响应到达后添加自定义的逻辑。OkHttp 使用了一个拦截器链来组织多个拦截器的执行顺序,这样就可以在请求和响应的不同阶段添加额外的行为。

Interceptor Chain

RealCall 的执行过程中,拦截器链通过 RealInterceptorChain 类实现。每一个 Interceptor 都可以访问到请求和响应,并有机会修改它们。拦截器链中的最后一个拦截器是 NetworkInterceptor,它负责实际的网络请求。

Java

1// RealInterceptorChain.java
2public Response proceed(Request request) throws IOException {
3    if (index == interceptors.size()) throw new AssertionError("no network interceptor");
4
5    // Call the next interceptor in the chain.
6    Interceptor interceptor = interceptors.get(index);
7    index++;
8
9    if (interceptor == networkInterceptor) {
10        // This is the last interceptor. Time to make the network call!
11        RealConnection connection = connect();
12        return withConnection(connection, new Callable<Response>() {
13            @Override public Response call() throws IOException {
14                return networkInterceptor.intercept(networkChain.proceed(request));
15            }
16        });
17    } else {
18        // Not the last interceptor. Proceed down the chain.
19        return interceptor.intercept(this);
20    }
21}

连接池(Connection Pool)

OkHttp 使用连接池来复用 HTTP 连接,这对于提高应用性能至关重要,尤其是对于需要频繁发起网络请求的场景。连接池中的连接可以被重复使用,从而避免了频繁创建和销毁连接所带来的开销。

ConnectionPool

ConnectionPool 是 OkHttp 中管理连接复用的核心组件。它会缓存空闲的连接,并在需要时提供给请求使用。当连接不再需要时,它们会被放回连接池,直到达到最大限制或连接过期。

Java
1// ConnectionPool.java
2public synchronized void put(Connection connection) {
3    if (connection == null) throw new NullPointerException("connection == null");
4    if (!connection.isEligibleForPool()) return;
5
6    String routeKey = connection.route().hostAddress();
7    Evictor<Connection> evictor = evictors.get(routeKey);
8    if (evictor == null) {
9        evictor = new Evictor<>(this, routeKey);
10        evictors.put(routeKey, evictor);
11    }
12    evictor.put(connection);
13}

重试机制

OkHttp 支持自动重试,可以在网络不稳定或服务器暂时不可用的情况下自动重发请求。重试机制是通过拦截器实现的,可以配置重试次数和重试条件。

RetryAndFollowUpInterceptor

RetryAndFollowUpInterceptor 是 OkHttp 内置的一个拦截器,用于处理重试和重定向。它会根据网络状况和服务器响应的状态码决定是否重试请求。

Java

1// RetryAndFollowUpInterceptor.java
2public Response intercept(Chain chain) throws IOException {
3    Request request = chain.request();
4    int attemptNumber = 1;
5
6    while (true) {
7        Response response = chain.proceed(request);
8
9        if (attemptNumber > retryCount) break;
10
11        if (response.isSuccessful()) break;
12
13        // Decide whether to retry based on the response code and other conditions.
14        if (shouldRetry(response, attemptNumber)) {
15            // Close the socket before retrying.
16            response.close();
17            request = getNextRequest(response, request);
18            attemptNumber++;
19        } else {
20            break;
21        }
22    }
23
24    return response;
25}

缓存

OkHttp 还支持响应缓存,可以将服务器响应存储在本地,以减少未来的网络请求。缓存的实现涉及多个组件,包括 Cache 类和相关的拦截器。

Cache

Cache 类用于管理缓存文件和元数据。它遵循 HTTP 协议中的缓存控制规则,以决定哪些响应可以被缓存,以及何时重新验证缓存的有效性。

Java
1// Cache.java
2public Response get(Request request) throws IOException {
3    // Check if we have a cached response that can be used.
4    // If so, return it.
5    // Otherwise, return null.
6}
7
8public void update(Response networkResponse, Response cacheResponse) throws IOException {
9    // Update the cache with the latest network response.
10}

TLS 和 SSL/TLS 协商

OkHttp 支持 HTTPS 协议,即安全的 HTTP 协议,这要求客户端与服务器之间进行 SSL/TLS 握手。OkHttp 使用 Java 的 SSLSocketFactorySSLContext 来处理 TLS 连接的建立。

SSLSocketFactory 和 SSLSession

在 OkHttp 中,OkHttpClient.Builder 提供了 sslSocketFactory(SSLSocketFactory, X509TrustManager) 方法,允许开发者自定义 SSLSocketFactory 和信任管理器。这使得 OkHttp 能够处理自签名证书、过期证书或任何其他不被标准信任库接受的证书。

Java
1// OkHttpClient.Builder.java
2public OkHttpClient.Builder sslSocketFactory(SSLSocketFactory sslSocketFactory,
3                                            X509TrustManager trustManager) {
4    if (sslSocketFactory == null) throw new NullPointerException("sslSocketFactory == null");
5    if (trustManager == null) throw new NullPointerException("trustManager == null");
6    this.sslSocketFactory = sslSocketFactory;
7    this.trustManager = trustManager;
8    return this;
9}

DNS 解析

OkHttp 支持多种 DNS 解析策略,包括标准的 DNS 解析、DNSSEC、IPv6 和多播 DNS。通过 Dns 接口,OkHttp 允许开发者自定义 DNS 解析行为。

Dns 接口

Dns 接口定义了 lookup 方法,该方法返回一个主机名的 IP 地址列表。OkHttp 默认使用系统的 DNS 解析器,但可以通过 OkHttpClient.Builderdns(Dns) 方法来替换。

Java
1// OkHttpClient.Builder.java
2public OkHttpClient.Builder dns(Dns dns) {
3    if (dns == null) throw new NullPointerException("dns == null");
4    this.dns = dns;
5    return this;
6}

超时

OkHttp 提供了全面的超时控制,包括连接超时、读取超时和写入超时。这些超时策略有助于处理网络延迟和服务器响应缓慢的问题。

超时配置

通过 OkHttpClient.Builder,可以设置全局的超时策略,也可以在每个请求中单独指定超时时间。

Java
1// OkHttpClient.Builder.java
2public OkHttpClient.Builder connectTimeout(int duration, TimeUnit unit) {
3    if (duration < 0) throw new IllegalArgumentException("duration < 0");
4    if (unit == null) throw new NullPointerException("unit == null");
5    this.connectTimeoutMillis = unit.toMillis(duration);
6    return this;
7}
8
9public OkHttpClient.Builder readTimeout(int duration, TimeUnit unit) {
10    if (duration < 0) throw new IllegalArgumentException("duration < 0");
11    if (unit == null) throw new NullPointerException("unit == null");
12    this.readTimeoutMillis = unit.toMillis(duration);
13    return this;
14}
15
16public OkHttpClient.Builder writeTimeout(int duration, TimeUnit unit) {
17    if (duration < 0) throw new IllegalArgumentException("duration < 0");
18    if (unit == null) throw new NullPointerException("unit == null");
19    this.writeTimeoutMillis = unit.toMillis(duration);
20    return this;
21}

HTTP/2 支持

OkHttp 自版本 3.0 开始支持 HTTP/2 协议,这是一种二进制、多路复用的 HTTP 协议,相比 HTTP/1.1 能够显著减少延迟和提高性能。

HTTP/2 连接

OkHttp 使用 Okio 库来实现高效的 I/O 操作,包括 HTTP/2 的帧处理和流控制。在 HTTP/2 连接中,多个请求可以在同一个 TCP 连接上并行处理,从而避免了 HTTP/1.1 中的队头阻塞问题。

Java
1// RealConnection.java
2public synchronized StreamAllocation newStream(boolean doNotRetry, boolean isMultiplexed) throws IOException {
3    // ...
4    if (isMultiplexed && !supportsMultiplexing()) throw new ProtocolException("Multiplexing not supported");
5
6    // ...
7    return new StreamAllocation(this, doNotRetry, isMultiplexed);
8}

性能优化

OkHttp 通过多种技术来优化网络请求的性能,包括:

  • 连接池:通过复用已有的 TCP 连接,避免了每次请求都要建立新连接的开销。
  • HTTP 缓存:通过遵循 HTTP 缓存控制头,减少不必要的网络往返。
  • 压缩:支持 HTTP 压缩,减少传输的数据量。
  • DNS 缓存:缓存 DNS 解析结果,减少 DNS 查询的时间。

流式上传和下载

OkHttp 支持流式上传和下载,这意味着在上传或下载大数据时,数据可以分块传输,而不是一次性加载到内存中。这对于处理大文件或长时间运行的请求尤其有用。

流式上传

在流式上传中,你可以使用 RequestBody 的子类 BufferedSink 来逐步写入数据,而不是一次性创建一个完整的 RequestBody 对象。

Java
1// 创建一个流式上传的 RequestBody
2RequestBody requestBody = new RequestBody() {
3    @Override
4    public MediaType contentType() {
5        return MediaType.parse("application/octet-stream");
6    }
7
8    @Override
9    public void writeTo(BufferedSink sink) throws IOException {
10        // 逐块写入数据
11        byte[] buffer = new byte[1024];
12        FileInputStream fis = new FileInputStream("largefile.dat");
13        int length;
14        while ((length = fis.read(buffer)) != -1) {
15            sink.write(buffer, 0, length);
16        }
17        fis.close();
18    }
19};
流式下载

流式下载允许你逐块读取响应体,而无需一次性加载整个响应到内存中。

Java
1// 创建一个流式下载的请求
2Request request = new Request.Builder()
3    .url("https://example.com/largefile")
4    .build();
5
6// 执行请求并逐块读取响应
7Response response = client.newCall(request).execute();
8ResponseBody body = response.body();
9if (body != null) {
10    BufferedSource source = body.source();
11    byte[] buffer = new byte[1024];
12    FileOutputStream fos = new FileOutputStream("output.dat");
13    long totalBytesRead = 0L;
14    int read;
15    while ((read = source.read(buffer)) != -1) {
16        totalBytesRead += read;
17        fos.write(buffer, 0, read);
18    }
19    fos.close();
20}

身份验证

OkHttp 支持多种身份验证机制,包括基本认证、摘要认证、OAuth 等。

基本认证
Java
1// 使用基本认证的请求
2String credentials = "username:password";
3String encodedCredentials = Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);
4Request request = new Request.Builder()
5    .url("https://example.com/api")
6    .header("Authorization", "Basic " + encodedCredentials)
7    .build();

跨域资源共享(CORS)

在处理跨域请求时,OkHttp 可以帮助你处理预检请求(OPTIONS 请求),这是 CORS 的一部分,用于确定是否允许跨域请求。

处理预检请求

在服务器端,你需要确保响应 OPTIONS 请求,并设置适当的 CORS 头部,如 Access-Control-Allow-OriginAccess-Control-Allow-MethodsAccess-Control-Allow-Headers。在客户端,OkHttp 会自动处理这些预检请求,你只需要正常发起你的主请求即可。

日志记录

OkHttp 提供了一个内置的日志拦截器,可以帮助你调试网络请求和响应。

Java
1// 添加日志拦截器
2HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
3loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
4client = new OkHttpClient.Builder()
5    .addInterceptor(loggingInterceptor)
6    .build();

性能监控

为了确保 OkHttp 的性能,你可能需要监控网络请求的指标,如响应时间、请求成功率和失败原因。OkHttp 提供了多种方式来收集这些数据。

使用 Listeners

OkHttp 的 EventListener 允许你监听网络请求的生命周期事件,如请求开始、响应接收、连接建立等。这可以帮助你收集性能数据和调试网络问题。

Java

1// 创建一个 EventListener 来监听请求事件
2class MyEventListener extends EventListener {
3    @Override
4    public void callStart(Call call) {
5        // 请求开始
6    }
7
8    @Override
9    public void responseReceived(Call call, Response response) {
10        // 响应接收
11    }
12
13    // 更多事件...
14}
15
16// 添加 EventListener 到 OkHttpClient
17OkHttpClient client = new OkHttpClient.Builder()
18    .eventListenerFactory(() -> new MyEventListener())
19    .build();

最佳实践

在使用 OkHttp 时,有一些最佳实践可以帮助你构建更健壮、更高效的网络层:

  • 使用连接池:始终使用连接池来复用连接,避免频繁的连接建立和关闭。
  • 处理异常:确保你的代码能够优雅地处理网络异常,如超时、中断和服务器错误。
  • 缓存策略:合理使用缓存,遵循 HTTP 缓存控制头,减少不必要的网络往返。
  • 安全:始终使用 HTTPS,确保数据传输的安全。
  • 资源回收:确保在使用完 ResponseBody 或其他资源后调用 close() 方法,以避免内存泄漏。

通过深入理解 OkHttp 的这些特性和最佳实践,你可以构建出既高效又可靠的网络请求层,为你的应用程序提供坚实的基础。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/44088.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Swift 数据类型

Swift 数据类型 Swift 是一种强类型语言,这意味着在 Swift 中声明的每个变量和常量都必须具有明确的类型。Swift 的类型系统旨在帮助开发者编写清晰、安全的代码。本文将详细介绍 Swift 中的基本数据类型,包括整数、浮点数、布尔值、字符和字符串。 整数类型 Swift 提供了…

音频语言学习领域数据集现状、分类及评估

Audio Language Learning (Audio-Text Learning) 是一个新兴的研究领域&#xff0c;专注于处理、理解和描述声音。它的发展动力是机器学习技术的进步以及越来越多地将声音与其相应的文本描述相结合的数据集的可用性。 Audio Language Models (ALMs) 是这个领域的关键技术&#…

MATLAB中的SDPT3、LMILab、SeDuMi工具箱

MATLAB中的SDPT3、LMILab、SeDuMi工具箱都是用于解决特定数学优化问题的工具箱&#xff0c;它们在控制系统设计、机器学习、信号处理等领域有广泛的应用。以下是对这三个工具箱的详细介绍&#xff1a; 1. SDPT3工具箱 简介&#xff1a; SDPT3&#xff08;Semidefinite Progra…

基于QT开发的反射内存小工具

前言 最近项目需要需要开发一个反射内存小工具&#xff0c;经过2天的修修改终于完成了。界面如下&#xff1a; 功能简介 反射内存指定地址数据读取反射内存指定地址数据写入反射内存指定地址数据清理十进制、十六进制、二进制数据相互转换 部分代码 void RfmMain::setWOthe…

SqlSugar-使用SqlSugar进行多数据库操作

使用SqlSugar进行多数据库操作主要涉及以下几个步骤&#xff1a; 1. 配置数据库连接 首先&#xff0c;你需要在项目的配置文件中&#xff08;如appsettings.json、web.config或app.config&#xff09;配置多个数据库的连接字符串。每个连接字符串都对应一个不同的数据库。 例…

攻防世界(PHP过滤器过滤)file_include

转换过滤器官方文档&#xff1a;https://www.php.net/manual/zh/filters.convert.php#filters.convert.iconv 这道题因为convert.base64-encode被过滤掉了&#xff0c;所以使用convert.iconv.*过滤器 在激活 iconv 的前提下可以使用 convert.iconv.* 压缩过滤器&#xff0c; 等…

Win10安装MongoDB(详细版)

文章目录 1、安装MongoDB Server1.1. 下载1.2. 安装 2、手动安装MongoDB Compass(GUI可视工具)2.1. 下载2.2.安装 3、测试连接3.1.MongoDB Compass 连接3.2.使用Navicat连接 1、安装MongoDB Server 1.1. 下载 官网下载地址 https://www.mongodb.com/try/download/community …

【第28章】MyBatis-Plus之插件主体

文章目录 前言一、MybatisPlusInterceptor 概览1. 属性2. InnerInterceptor 接口 二、使用示例1.Spring 配置2.Spring Boot 配置3 .mybatis-config.xml 配置 三、拦截忽略注解 InterceptorIgnore四、手动设置拦截器忽略执行策略五、本地缓存 SQL 解析总结 前言 MyBatis-Plus 提…

android 固定图片大小

在Android中&#xff0c;固定图片大小可以通过多种方法实现&#xff0c;这些方法主要涉及到ImageView控件的使用、Bitmap类的操作&#xff0c;以及第三方库&#xff08;如Glide&#xff09;的辅助。以下是几种常见的方法&#xff1a; 1. 使用ImageView控件 在Android的布局文…

利用docker容器安装node,使用vue的开发环境

目录 vue-app ├── docker-data │ ├── site │ ├── app ├── docker-compose.yaml └── deploy.sh docker-compose.yaml yaml文件执行 version: 3.8services:node:image: node:latestcontainer_name: vue-appports:- "8080:8080" # 宿主8080映射容器8…

系统服务综合项目

要求&#xff1a; 现有主机 node01 和 node02&#xff0c;完成如下需求&#xff1a; 1、在 node01 主机上提供 DNS 和 WEB 服务 2、dns 服务提供本实验所有主机名解析 3、web服务提供 www.rhce.com 虚拟主机 4、该虚拟主机的documentroot目录在 /nfs/rhce 目录 5、该目录由 no…

如何保证语音芯片的稳定性能和延长使用寿命

要让语音芯片保持稳定性能&#xff0c;首先需要深入理解其工作原理和内部构造。语音芯片&#xff0c;作为现代电子设备中的核心组件之一&#xff0c;承载着声音信号的处理与输出功能。为了确保其稳定运行&#xff0c;我们需要从多个方面进行细致的考虑和操作。‌ 1、避免长期高…

Windows系统MySQL的安装,客户端工具Navicat的安装

下载mysql安装包&#xff0c;可以去官网下载&#xff1a;www.mysql.com。点击downloads 什么&#xff1f;后面还有福利&#xff1f; 下载MySQL 下载企业版&#xff1a; 下载Windows版 5点多的版本有点低&#xff0c;下载8.0.38版本的。Window系统。下载下面的企业版。不下载…

乡镇集装箱生活污水处理设备处理效率高

乡镇集装箱生活污水处理设备处理效率高 乡镇集装箱生活污水处理设备优势 结构紧凑&#xff1a;集装箱式设计减少了占地面积&#xff0c;便于在土地资源紧张的乡镇地区部署。 安装方便&#xff1a;设备出厂前已完成组装和调试&#xff0c;现场只需进行简单的连接和调试即可投入使…

[数字图像处理]基础知识整理(部分,持续更新)

程序中描述一副图像&#xff0c;已知其横向纵向的像素个数即可&#xff08;&#xff09; 灰度直方图能反映一副图像各个灰度级像素占图像的面积比&#xff08;√&#xff09; 从程序编写的角度看&#xff0c;描述一副图像的基本属性通常包括其分辨率&#xff0c;即图像的宽度…

Docker镜像和容器的管理

1 Docker镜像管理操作 开启镜像加速 根据关键字查询镜像 下载查看镜像 详细镜像信息 查看latest版本 上传镜像到阿里云仓库 2 Docker容器操作 关于容器根据第一个pid进程是否能正常在前台运行

19. 地址转换

地址转换 题目描述 Excel 是最常用的办公软件。每个单元格都有唯一的地址表示。比如&#xff1a;第 12 行第 4 列表示为&#xff1a;"D12"&#xff0c;第 5 行第 255 列表示为"IU5"。 事实上&#xff0c;Excel 提供了两种地址表示方法&#xff0c;还有一…

算法训练营第30天|122.买卖股票的最佳时机II|55. 跳跃游戏|45.跳跃游戏II|1005.K次取反后最大化的数组和

122.买卖股票的最佳时机II 思路&#xff1a;只有前一天与后一天的利润为正时&#xff0c;才将其加入总利润。 55. 跳跃游戏 思路&#xff1a;找最大覆盖范围 出错点&#xff1a;数组的遍历&#xff0c;遍历范围应该是覆盖范围内 45.跳跃游戏II 思路&#xff1a; 局部最优&am…

批量爬取B站网络视频信息

使用XPath爬取B站视频链接等相关信息 分析B站html框架获取内容完整代码 对于B站&#xff0c;目前网上的爬虫大多都是使用通过解析服务器的响应来爬取想要的内容&#xff0c;下面我们通过使用XPath来爬取B站上一些想要的信息 此次任务我们需要对B站搜索到的关键字&#xff0c;并…

数据结构 —— FloydWarshall算法

数据结构 —— FloydWarshall算法 FloydWarshall算法三种最短路径算法比较1. Dijkstra算法2. Bellman-Ford算法3. Floyd-Warshall算法总结 我们之前介绍的两种最短路径算法都是单源最短路径&#xff0c;就是我们要指定一个起点来寻找最短路径&#xff0c;而我们今天介绍的Floyd…