OkHttp 请求网络服务器流程源码分析

概述

HTTP是现代应用常用的一种应用程序与服务器交换网络数据的方式,高效地使用HTTP能让资源加载更快,节省带宽。OkHttp是一个高效的HTTP客户端,它有以下默认特性:

  • 支持HTTP/2,允许所有同一个主机地址的请求共享同一个socket连接;
  • 连接池减少请求时握手延时;
  • 透明的GZIP压缩减少响应数据的大小;
  • 缓存响应内容,避免一些完全重复的请求;

当网络出现问题的时候OkHttp会自动恢复一般的连接问题,如果你的服务有多个IP地址,当第一个IP请求失败时,OkHttp会交替尝试来接你配置的其他IP地址,OkHttp默认使用现代TLS技术(TLS 1.3,ALPN,certificate pinning)初始化新的连接,当握手失败时会回退到TLS 1.0,这样做能实现更广泛的连接。

OSI和TCP参考模型图


osi和tcp模型.png

总体流程

整体流程:OkHttpClient构建的Request转换为Call,然后在RealCall中进行异步(AsyncCall)或同步任务,最后通过一系列的拦截器(interceptor)发出网络请求然后递归返回的response,大概就是这么个流程,,下面是OKHttp总体请求的流程时序图,包括同步和异步请求:


okhttp.png

上面的时序图是包括同步和异步的请求,而同步和异步请求的区别在于异步会开启线程池去请求服务器,其后面的逻辑就是基本一致都是调用getResponseWithInterceptorChain方法通过责任链模式的方式分别去通过每个链的节点处理Request,经过前面的节点处理过的Request会交给最后一个节点CallServerInterceptor去做真正的请求服务器的处理,最终拿到Response然后经过每个节点的处理,最终返回给client。

我们这里先看一个简单的同步的GET请求。

public class GetExample {
OkHttpClient client = new OkHttpClient.Builder()
        .dispatcher(
                new Dispatcher()
        ).build();

String run(String url) throws IOException {
    Request request = new Request.Builder()
            .url(url)
            .build();

    try (Response response = client.newCall(request).execute()) {
        return response.body().string();
    }
}

public static void main(String[] args) throws IOException {
    GetExample example = new GetExample();
    String response = example.run("https://raw.github.com/square/okhttp/master/README.md");
    System.out.println(response);
    }
}

Okhttp的请求无非就是构造OkHttpClient 和Request,然后直接发起请求,Response response = client.newCall(request).execute()这是一个同步的请求,会阻塞当前线程,并返回Response,那么看看client.newCall(request).execute()到底做了什么?

  //OkHttpClient 
 @Override
public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
}


 //RealCall 
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.transmitter = new Transmitter(client, call);
    return call;
}

开始OkHttpClient .newCall方法创建Call,Call是一个接口,它的实现类是RealCall,接着RealCall调用execute方法,代码如下所示。

  //RealCall 
@Override
public Response execute() throws IOException {
    synchronized (this) {
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
    }
    transmitter.timeoutEnter();
    transmitter.callStart();
    try {
        client.dispatcher().executed(this);
        return getResponseWithInterceptorChain();//拦截器链的开始点
    } finally {
        client.dispatcher().finished(this);
    }
}

在execute方法中,首先判断该Call是否已经被执行了,如果已经被执行将抛出异常,然后调用dispatcher的executed方法,将call放入Dispatcher的同步队列runningSyncCalls中,接着在finish块中将call移除runningSyncCalls,即请求结束,那整个网络请求的一系列操作到底在哪,接着看RealCall.getResponseWithInterceptorChain方法?

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    //重试和重定向的拦截器
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    //处理请求头的拦截器,如gzip
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //缓存的处理的拦截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //建立连接的拦截器
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
        interceptors.addAll(client.networkInterceptors());
    }
    //和服务器数据交换的拦截器
    interceptors.add(new CallServerInterceptor(forWebSocket));


    //链的头
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
            originalRequest, this, client.connectTimeoutMillis(),
            client.readTimeoutMillis(), client.writeTimeoutMillis());
    boolean calledNoMoreExchanges = false;
    try {

        //拦截器链的开始
        Response response = chain.proceed(originalRequest);

        if (transmitter.isCanceled()) {
            closeQuietly(response);
            throw new IOException("Canceled");
        }
        return response;
    } catch (IOException e) {
        calledNoMoreExchanges = true;
        throw transmitter.noMoreExchanges(e);
    } finally {
        if (!calledNoMoreExchanges) {
            transmitter.noMoreExchanges(null);
        }
    }
}

在getResponseWithInterceptorChain方法中,主要是构造拦截器链RealInterceptorChain,紧接着 调用chain.proceed(originalRequest)方法开始执行拦截器链,代码如下。

@Override
public Response proceed(Request request) throws IOException {
    return proceed(request, transmitter, exchange);
}

public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
        throws IOException {  
    
       //省略很多代码..................

    // Call the next interceptor in the chain.
    //调用链中的下一个拦截器。
    RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
            index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    //省略很多代码..................

    //一条链节点结束
    return response;
}

可看到在proceed方法中,又重新构造新的拦截器链RealInterceptorChain实例next ,接着取出拦截器并调用了interceptor方法,并将next 传进去,我们选择第一个拦截器RetryAndFollowUpInterceptor看看intercept方法到底做了什么?

RetryAndFollowUpInterceptor 负责失败重试以及重定向

 @Override
public Response intercept(Chain chain) throws IOException {

    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Transmitter transmitter = realChain.transmitter();

    int followUpCount = 0;
    //重复请求响应,如果是重定向,就会有这个priorResponse,HTTP redirect or authorization
    Response priorResponse = null;
    while (true) {
        //预创建一个stream来承载request。 如果已经存在了connection,则优先使用现有connection。
        transmitter.prepareToConnect(request);

        if (transmitter.isCanceled()) {
            throw new IOException("Canceled");
        }

        Response response;

        //为true说明建立连接是成功的,并且返回了response
        boolean success = false;
        try {
            //当执行后面的操作抛出异常,可能我们需要重试连接
            response = realChain.proceed(request, transmitter, null);
            success = true;
        } catch (RouteException e) {
            //如果是Router连接失败,那么请求将不会再发送,直接把异常跑到上层应用
            if (!recover(e.getLastConnectException(), transmitter, false, request)) {
                throw e.getFirstConnectException();
            }
            //那么继续重新连接
            continue;
        } catch (IOException e) {
            // 与服务器尝试通信失败,请求不会再发送。
            boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
            //判断需不需要重试
            if (!recover(e, transmitter, requestSendStarted, request)) throw e;
            continue;
        } finally {
            // //抛出未检查的异常,释放资源.
            if (!success) {
                transmitter.exchangeDoneDueToException();
            }
        }

        // 满足先前存在的priorResponse,也就是第一次请求服务器时,服务器返回的响应就仅仅是状态码没有任何的数据。这样的response从来就没有body
        if (priorResponse != null) {
            response = response.newBuilder()
                    .priorResponse(priorResponse.newBuilder()
                            .body(null)
                            .build())
                    .build();
        }

        Exchange exchange = Internal.instance.exchange(response);
        Route route = exchange != null ? exchange.connection().route() : null;

        /**
         * 计算出响应接收userResponse的HTTP请求。
         * 这将添加身份验证标头,遵循重定向或处理客户端请求超时。
         * 如果后续操作不必要或不适用,则返回null。
         *
         * 注释说那么多,其实就是根据Http状态码决定重定向
         */
        Request followUp = followUpRequest(response, route);


        //不需要重定向
        if (followUp == null) {
            if (exchange != null && exchange.isDuplex()) {
                transmitter.timeoutEarlyExit();
            }
            return response;
        }


        RequestBody followUpBody = followUp.body();
        if (followUpBody != null && followUpBody.isOneShot()) {
            return response;
        }

        closeQuietly(response.body());

        if (transmitter.hasExchange()) {
            exchange.detachWithViolence();
        }

        if (++followUpCount > MAX_FOLLOW_UPS) {
            throw new ProtocolException("Too many follow-up requests: " + followUpCount);
        }

        request = followUp;
        priorResponse = response;
    }
}

1、第一步,根据 response = realChain.proceed(request, transmitter, null);中直接调用了下一个拦截器,然后捕获可能的异常来进行相应的重试操作或者请求失败。
2、根据第一步请求得到response,调用Request followUp = followUpRequest(response, route);followUp是Request类型,判断followUp 是否有重定向的操作,即followUp 不为null,就是有重定向,如果为null,说明不需要重定向,直接返回response。
3、根据第二步,followUp 如果不为null,说明需要进行重定向,并把response赋值给priorResponse和把followUp赋值给request,而priorResponse是指上一次循环的响应,在方法体最下面有赋值。主要是为了重定向。如果发现上一次request的响应有重定向,则给当前响应的priorResponse赋值,并且把response的body置为空,接着继续下一步的请求。

BridgeInterceptor

@Override
public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();


    //检查request。将用户的request转换为发送到server的请求
    RequestBody body = userRequest.body();
    if (body != null) {
        MediaType contentType = body.contentType();
        if (contentType != null) {
            //自动增添加请求头 Content-Type
            requestBuilder.header("Content-Type", contentType.toString());
        }
        //请求体内容的长度
        long contentLength = body.contentLength();
        //如果传输长度不为-1,则表示完整传输
        if (contentLength != -1) {
            //设置头信息 Content-Length
            requestBuilder.header("Content-Length", Long.toString(contentLength));
            requestBuilder.removeHeader("Transfer-Encoding");
        } else {
            //如果传输长度为-1,则表示分块传输,自动设置头信息
            requestBuilder.header("Transfer-Encoding", "chunked");
            requestBuilder.removeHeader("Content-Length");
        }
    }

    if (userRequest.header("Host") == null) {
        requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }

    //如果没有设置头信息 Connection,则自动设置为 Keep-Alive
    if (userRequest.header("Connection") == null) {
        requestBuilder.header("Connection", "Keep-Alive");
    }


    //如果我们添加“Accept-Encoding:gzip”头字段,我们还负责解压缩传输流。实际上默认Okhttp已经添加的,告诉服务器接收数据使用gzip
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
        //如果我们没有在请求头信息里增加Accept-Encoding,在这里会自动设置头信息 Accept-Encoding = gzip
        transparentGzip = true;
        requestBuilder.header("Accept-Encoding", "gzip");
    }
    //Cookie的处理
    List cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
        requestBuilder.header("Cookie", cookieHeader(cookies));
    }

    if (userRequest.header("User-Agent") == null) {
        requestBuilder.header("User-Agent", userAgent);
    }


    //执行下一步操作,得到Response
    Response networkResponse = chain.proceed(requestBuilder.build());

    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

    Response.Builder responseBuilder = networkResponse.newBuilder()
            .request(userRequest);


    //如果response的头信息里Content-Encoding = gzip,并且我们没有手动在请求头信息里设置 Accept-Encoding = gzip,
    // 则会进行 gzip 解压数据流
    if (transparentGzip && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
            && HttpHeaders.hasBody(networkResponse)) {
        //使用了GzipSource
        GzipSource responseBody = new GzipSource(networkResponse.body().source());

        Headers strippedHeaders = networkResponse.headers().newBuilder()
                .removeAll("Content-Encoding")
                .removeAll("Content-Length")
                .build();
        responseBuilder.headers(strippedHeaders);
        String contentType = networkResponse.header("Content-Type");
        responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
}

在BridgeInterceptor中主要是对request和response的header做处理,比如对request body做gzip压缩,这里需要注意的是,如果我们在代码里没有手动设置 Accept-Encoding = gzip ,那么 OkHttp 会自动处理 gzip 的解压缩;反之,你需要手动对返回的数据流进行 gzip 解压缩。

CacheInterceptor

@Override
public Response intercept(Chain chain) throws IOException {

    //获取上一次缓存的response,有可能为null
    Response cacheCandidate = cache != null ? cache.get(chain.request()) : null;


    //获取到当前时间
    long now = System.currentTimeMillis();

    //long nowMillis, Request request, Response cacheResponse
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    //networkRequest 不为null,说明强制缓存规则不生效
    Request networkRequest = strategy.networkRequest;
    //该请求的上次的缓存的结果
    Response cacheResponse = strategy.cacheResponse;

    if (cache != null) {
        cache.trackResponse(strategy);
    }

    if (cacheCandidate != null && cacheResponse == null) {
        closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
    //从缓存策略上来说强制缓存生效,应该直接取上次缓存结果,但由于未知原因缓存的结果没有了或者上次返回就没有,这里直接返回了失败的Response
    if (networkRequest == null && cacheResponse == null) {
        return new Response.Builder()
                .request(chain.request())
                .protocol(Protocol.HTTP_1_1)
                .code(504)
                .message("Unsatisfiable Request (only-if-cached)")
                .body(Util.EMPTY_RESPONSE)
                .sentRequestAtMillis(-1L)
                .receivedResponseAtMillis(System.currentTimeMillis())
                .build();
    }

    // If we don't need the network, we're done.
    //强制缓存生效,按道理来说我们直接返回上次的cacheResponse就可以了,
    // 当然这里对Response的cacheResponse的body做了置空处理。
    if (networkRequest == null) {
        return cacheResponse.newBuilder()
                .cacheResponse(stripBody(cacheResponse))
                .build();
    }


    //强制缓存不生效,说明此时应该使用对比缓存,需要从服务端取数据:
    Response networkResponse = null;
    try {
        //执行网络
        networkResponse = chain.proceed(networkRequest);
    } finally {
        // If we're crashing on I/O or otherwise, don't leak the cache body.
        if (networkResponse == null && cacheCandidate != null) {
            closeQuietly(cacheCandidate.body());
        }
    }

    // If we have a cache response too, then we're doing a conditional get.
    //按照对比缓存规则,取完之后应该判断是否返回304,如果是应该使用缓存结果:
    if (cacheResponse != null) {
        if (networkResponse.code() == HTTP_NOT_MODIFIED) {
            Response response = cacheResponse.newBuilder()
                    .headers(combine(cacheResponse.headers(), networkResponse.headers()))
                    .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
                    .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
                    .cacheResponse(stripBody(cacheResponse))
                    .networkResponse(stripBody(networkResponse))
                    .build();
            networkResponse.body().close();

            // Update the cache after combining headers but before stripping the
            // Content-Encoding header (as performed by initContentStream()).
            cache.trackConditionalCacheHit();

            //更新缓存
            cache.update(cacheResponse, response);
            return response;
        } else {
            closeQuietly(cacheResponse.body());
        }
    }


    //缓存完全失效了,这个时候应该利用刚才网络请求的结果
    Response response = networkResponse.newBuilder()
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();

    if (cache != null) {
        if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
            // Offer this request to the cache.
            //保存到缓存中
            CacheRequest cacheRequest = cache.put(response);
            return cacheWritingResponse(cacheRequest, response);
        }


        //过滤不能做缓存的请求method
        if (HttpMethod.invalidatesCache(networkRequest.method())) {
            try {
                cache.remove(networkRequest);
            } catch (IOException ignored) {
                // The cache cannot be written.
            }
        }
    }

    return response;
}

http缓存分为两种:

  • 一种强制缓存,一种对比缓存,强制缓存生效时直接使用以前的请求结果,无需发起网络请求。
  • 对比缓存生效时,无论怎样都会发起网络请求,如果请求结果未改变,即服务端会返回304,但不会返回数据,数据从缓存中取,如果改变了会返回数据,那么就要对比缓存了。

在CacheInterceptor中,networkRequest如果不为null,则说明强制缓存生效,也就意味着这次请求直接使用上次的结果,无需发起网络请求。而cacheResponse则是上次的缓存结果。整个缓存处理其实就是这两种情况的组合,当然这里就不展开讲,因为我们是将的请求过程。

ConnectInterceptor

@Override 
public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    Transmitter transmitter = realChain.transmitter();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);

    return realChain.proceed(request, transmitter, exchange);
}

代码很少,其实它所有的连接逻辑都放到了transmitter.newExchange方法中,这里细节有点复杂,反正就是建立socket连接各种机制,这会在下一篇文章中讲,这里不展开讲。

CallServerInterceptor

@Override
public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;

    //封装请求头,即conent-length,method,编码等等
    Exchange exchange = realChain.exchange();
    Request request = realChain.request();

    //当前时间
    long sentRequestMillis = System.currentTimeMillis();

    exchange.writeRequestHeaders(request);


    /**
     * http 100-continue用于客户端在发送POST数据给服务器前,征询服务器情况,
     * 看服务器是否处理POST的数据,如果不处理,客户端则不上传POST数据,如果处理,
     * 则POST上传数据。在现实应用中,通过在POST大数据时,才会使用100-continue协议。
     *
     * 如果客户端有POST数据要上传,可以考虑使用100-continue协议。加入头{"Expect":"100-continue"}
     * 如果没有POST数据,不能使用100-continue协议,因为这会让服务端造成误解。
     * 并不是所有的Server都会正确实现100-continue协议,如果Client发送Expect:100-continue消息后,在timeout时间内无响应,Client需要立马上传POST数据。
     * 有些Server会错误实现100-continue协议,在不需要此协议时返回100,此时客户端应该忽略。
     *
     *
     */
    boolean responseHeadersStarted = false;
    //当前request需要请求体,即post请求等方式,如果有,则进行封装
    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
        // 判断服务器是否允许发送body
        if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
            //sink写入请求
            exchange.flushRequest();
            responseHeadersStarted = true;
            //这里只是发送的header
            exchange.responseHeadersStart();
            //读取response
            responseBuilder = exchange.readResponseHeaders(true);
        }

        if (responseBuilder == null) {
            //向服务器发送requestbody,还判断了是否多路复用,不是全双工通讯,智能请求第一次就释放资源
            if (request.body().isDuplex()) {
                // Prepare a duplex body so that the application can send a request body later.
                exchange.flushRequest();
                BufferedSink bufferedRequestBody = Okio.buffer(
                        exchange.createRequestBody(request, true));
                request.body().writeTo(bufferedRequestBody);
            } else {
                // Write the request body if the "Expect: 100-continue" expectation was met.
                BufferedSink bufferedRequestBody = Okio.buffer(
                        exchange.createRequestBody(request, false));
                request.body().writeTo(bufferedRequestBody);
                //不是全双工通讯,智能请求第一次就释放资源
                bufferedRequestBody.close();
            }
        } else {//不发送RequestBody
            exchange.noRequestBody();
            if (!exchange.connection().isMultiplexed()) {
                // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
                // from being reused. Otherwise we're still obligated to transmit the request body to
                // leave the connection in a consistent state.
                exchange.noNewExchangesOnConnection();
            }
        }
    } else {//不发送RequestBody
        exchange.noRequestBody();
    }

    if (request.body() == null || !request.body().isDuplex()) {
        //就是请求
        exchange.finishRequest();
    }

    if (!responseHeadersStarted) {
        exchange.responseHeadersStart();
    }

    if (responseBuilder == null) {
        responseBuilder = exchange.readResponseHeaders(false);
    }

    //返回response
    Response response = responseBuilder
            .request(request)
            .handshake(exchange.connection().handshake())
            .sentRequestAtMillis(sentRequestMillis)
            .receivedResponseAtMillis(System.currentTimeMillis())
            .build();

    int code = response.code();

    //100的状态码的处理继续发送请求,继续接受数据
    if (code == 100) {
        // server sent a 100-continue even though we did not request one.
        // try again to read the actual response
        response = exchange.readResponseHeaders(false)
                .request(request)
                .handshake(exchange.connection().handshake())
                .sentRequestAtMillis(sentRequestMillis)
                .receivedResponseAtMillis(System.currentTimeMillis())
                .build();

        code = response.code();
    }

    exchange.responseHeadersEnd(response);

    if (forWebSocket && code == 101) {
        // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
        response = response.newBuilder()
                .body(Util.EMPTY_RESPONSE)
                .build();
    } else {
        response = response.newBuilder()
                .body(exchange.openResponseBody(response))
                .build();
    }

    if ("close".equalsIgnoreCase(response.request().header("Connection"))
            || "close".equalsIgnoreCase(response.header("Connection"))) {
        exchange.noNewExchangesOnConnection();
    }

    //返回为空的处理
    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
        throw new ProtocolException(
                "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
}
  • 判断是否允许上传requestBody, 写入http请求头信息;

  • 判断header中Expect域是否为100-continue,这个请求头字段的作用是在发送RequestBody前向服务器确认是否接受RequestBody,如果没有,则正常请求。如果有,则相当于一次简单的握手操作,则等待服务器返回的ResponseHeaders之后再继续,如果服务器接收RequestBody,会返回null;

  • 如果RequestBuilder为null,说明Expect不为100-continue或者服务器同意接收RequestBody。这时就开始向流中写入RequestBody;

  • 读取响应头信息 构建Response,写入原请求、握手情况、请求时间、得到结果的时间等;

  • 针对204/205状态码处理;

最后就结束了,可能后面还会两篇文章介绍CacheInterceptor和ConnectionInterceptor这两个拦截器,看一下整个网络请求的时序图:


okhttp.png

这张图是画的同步请求的,其实同步和异步请求其实请求过程的处理是一样的,只不过异步请求前期会做一些并发的处理,其余在拦截器那部分都是一样的,感兴趣的话,可以去看看执行异步的源码,谢谢。

你可能感兴趣的:(OkHttp 请求网络服务器流程源码分析)