Okhttp实现原理

avatar
作者
筋斗云
阅读量:3

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 的这些特性和最佳实践,你可以构建出既高效又可靠的网络请求层,为你的应用程序提供坚实的基础。

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!