Okhttp3源码分析(3) Interceptor拦截器

OKHttp3源码分析——学习笔记 文章目录1. HttpClient与HttpUrlConnection的区别2. OKHttp源码分析使用步骤:dispatcher任务调度器,Request请求RealCallAsyncCall3. OKHttp架构分析1. 异步请求线程池, Dispather2. 连接池清理线程池- ConnectionPool3. 缓存整理线程池 DisLruCache4. Http2异步事务线程池, ht... 阅读详情

okhttp3源码分析基于okhttp3.10.0。

在前面章节里提到过,okhttp不管是同步请求还是异步请求,最终都是通过RealCall.getResponseWithInterceptorChain()方法获取请求响应的,该方法的核心功能就是要在本章节介绍的okhttp的Interceptor拦截器的工作机制。

Interceptor介绍

关于okhttp的Interceptor拦截器在官网也有具体说明:https://github.com/square/okhttp/wiki/Interceptors。

Interceptors are a powerful mechanism that can monitor, rewrite, and retry calls.

上面这句话是okhttp官网中对Interceptor功能的总结,翻译过来的意思就是 拦截器是一种强大的机制,可以监视,重写,重试调用。


为了更清晰的理解拦截器的作用,下面通过两个实例来说明Interceptor的具体作用,以便于后面更好的理解okhttp内置的五大拦截器。

自定义拦截器

要想实现一个自定义拦截器只需要实现Interceptor接口并重写intercept(Chain)接口即可。

自定义日志拦截器

这里引用官网的自定义日志拦截器LoggingInterceptor,主要实现请求信息和响应信息的输出,这个在实际开发中是很有意义的,通过在控制台输出请求响应,能提高接口调试的效率。

LoggingInterceptor如下:

    class LoggingInterceptor implements Interceptor {
        @Override public Response intercept(Interceptor.Chain chain) throws IOException {
            Request request = chain.request();

            // 打印请求信息:请求url,头部信息,连接信息
            long t1 = System.nanoTime();
            logger.info(String.format("Sending request %s on %s%n%s",
                    request.url(), chain.connection(), request.headers()));

            // 通过调用该方法获取响应
            Response response = chain.proceed(request);

            // 打印响应信息:请求url,请求时间,请求头
            long t2 = System.nanoTime();
            logger.info(String.format("Received response for %s in %.1fms%n%s",
                    response.request().url(), (t2 - t1) / 1e6d, response.headers()));

            return response;
        }
    }

如果想要在okhttp中使用该拦截器,只需要为OkHttpClient实例设置该拦截器即可,如下:

OkHttpClient client = new OkHttpClient.Builder()
    // 设置一个拦截器
    .addInterceptor(new LoggingInterceptor())
    // 设置一个网络拦截器
    .addaddNetworkInterceptor(new LogingInterceptor())
    .build();

为OkHttpClient实例设置拦截器有两种方式:一个通过addInterceptor()方法,一个是addaddNetworkInterceptor()方法。

关于这两种拦截器官网也有说明,这里仅仅展示一张图,更多的可以查看官网介绍。

自定义加解密拦截器

自定义编解码拦截器可以体现Interceptor的一大功能点:重写

关于重写的介绍,可以查看官网介绍:

实际需求:对于App中的支付功能,对传输安全的要求比较高,一般在网络交互过程都应该避免明文传入一些敏感的数据,例如支付金额,账户信息等,以防止支付信息被篡改从而导致损失。

基于上述的需求,这里可以通过实现一个自定义加解密Interceptor来实现请求信息的加密和响应信息的解密。之所以将加解密的过程通过拦截器来实现,最大的好处就是可以让业务层更专注于数据的创建,对开发者屏蔽密文便于开发过程中的调试。

下面看下该拦截器的具体实现。

/**
 * 实现自定义加解密拦截器
 */
public abstract class ParseInterceptor implements Interceptor {
    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {

        // 加密请求
        Request encrypt = encrypt(chain.request());

        Response response = chain.proceed(encrypt);

        // 解密响应
        Response decrypt = decrypt(response);
        return decrypt;
    }

    /**
     * 加密
     */
    private Request encrypt(Request originalRequest) {
        try {
            // 1、原 请求体,获取请求体中的具体值
            RequestBody originalBody = originalRequest.body();
            Buffer buffer = new Buffer();
            originalBody.writeTo(buffer);
            String originalStr = buffer.readUtf8();

            // 2、实现具体的请求体几加密
            String encryptStr = encrypt(originalStr);
            RequestBody newRequestBody = RequestBody.create(originalBody.contentType(), encryptStr);

            // 3、返回新的请求携带新的请求体
            return originalRequest.newBuilder()
                    .method(originalRequest.method(), newRequestBody)
                    .build();

        } catch (Exception e) {

        }
        return originalRequest;
    }

    /**
     * 解密
     */
    private Response decrypt(Response originalResponse) {
        try {

            ResponseBody originalResponseBody = originalResponse.body();
            if (originalResponseBody == null || originalResponseBody.contentType() == null) {
                return originalResponse;
            }

            String decrypt = decrypt(originalResponseBody.string());

            ResponseBody newResponseBody = ResponseBody.create(originalResponseBody.contentType(), decrypt);

            return originalResponse.newBuilder()
                    .body(newResponseBody)
                    .build();
        } catch (Exception e) {

        }

        return originalResponse;

    }

    /**
     * 加密请求
     * 省略具体加密细节,例如MD5、RSA、DES对字符串进行加密
     */
    public abstract String encrypt(String response);

    /**
     * 解密响应
     * 省略具体解密细节,例如MD5、RSA、DES对字符串进行解密
     */
    public abstract String decrypt(String response);

}

该拦截器的使用方式也和日志拦截器一样,这里就不重复了。

Intercepteror工作流程

通过上述两个自定义Interceptor,相信对Okhttp的Interceptor的功能有了基本的认识。

现在,我们回到之前章节以及本章节开始提到的RealCall.getResponseWithInterceptorChain(),之前就提到过该方法就是用于获取网络响应的,其实该方法的作用远非如此简单。

Response getResponseWithInterceptorChain() throws IOException {
        // Build a full stack of interceptors.
        List<Interceptor> interceptors = new ArrayList<>();
        interceptors.addAll(client.interceptors()); // 添加自定义拦截器
        interceptors.add(retryAndFollowUpInterceptor);//重试和重定向拦截器
        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, null, null, null, 0,
                originalRequest, this, eventListener, client.connectTimeoutMillis(),
                client.readTimeoutMillis(), client.writeTimeoutMillis());

        // 通过拦截器链的proceed方法运行整个拦截器
        return chain.proceed(originalRequest);
    }

上面的getResponseWithInterceptorChain()方法,有两大作用:

  • 创建拦截器集合存放所有的拦截器,包括自定义拦截器和系统内部的五大拦截器。
  • 创建第一个拦截器链RealInterceptorChain实例,通过调用该方法的proceed(Request),让整个拦截器链运行起来。

这个方法是拦截器工作的起点,通过在该方法中创建第一个RealInterceptorChain并通过proceed()方法,让拦截器集合中的所有拦截器将会按照顺序依次执行,最终获取网络响应。

在上文中简述了拦截器及拦截器链的工作流程,下面就对拦截器及拦截器链做详细分析,内容如下:

  • 拦截器链Chain

    1. RealInterceptorChain
  • Okhttp3中的五大Interceptor

    1. RetryAndFollowUpInterceptor
    2. BridgeInterceptor
    3. CacheInterceptor
    4. ConnectInterceptor
    5. CallServerInterceptor

这里先通过一张图展示拦截器的执行顺序(这里忽略了自定义拦截器):
这里写图片描述

Interceptor接口及Chain接口

Interceptor接口比较简单,就定义了一个方法以及一个内部接口Chain,如下:

public interface Interceptor {
  // 核心方法
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();
    // 核心方法
    Response proceed(Request request) throws IOException;

    @Nullable Connection connection();

    Call call();

    int connectTimeoutMillis();

    Chain withConnectTimeout(int timeout, TimeUnit unit);

    int readTimeoutMillis();

    Chain withReadTimeout(int timeout, TimeUnit unit);

    int writeTimeoutMillis();

    Chain withWriteTimeout(int timeout, TimeUnit unit);
  }
}

对于Interceptor接口需要关注的方法就一个intercept(Chain)。
而对于Chain接口需要关注的方法也只有一个proceed(Request),这是让多个拦截器形成拦截器链工作的方法。

RealInterceptorChain

RealInterceptorChain是Interceptor.Chain接口的唯一实现类。

所以这里需要看下该类的具体逻辑,这里我们只需要看下proceed(Request)方法即可,这个方法的核心代码就三行,这也是让拦截器以链的形式依次执行的关键代码,如下:

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
  RealConnection connection) throws IOException {

    // 省略部分代码

        /**
         * 1、创建一个新的RealInterceptorChain,用于处理下一个Interceptor
         *  之所以是下一个这个通过(index+1)可知,每次调用依次都是在index基础上加1,这就可以构成一个拦截器链依次执行。
         这有点像递归的实现流程。
         */
        RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
                connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
                writeTimeout);
        /**
         * 2、获取当前RealInterceptorChain所需要处理的Interceptor
         *    
         *    第一次执行就是获取index为0的Interceptor,这个通过RealCall.getResponseWithInterceptorChain()方法中
         *    RealInterceptorChain实例的创建可知
         */
        Interceptor interceptor = interceptors.get(index);
        /**
         * 3、调用Interceptor的intercept(Chain)方法,这里会传入RealInterceptorChain,用户处理下一个Interceptor
         * 
         *  要让Interceptor完整的执行完成,在Interceptor中必须执行next.proceed(Request)
         */
        Response response = interceptor.intercept(next);
    // 省略部分代码

return response;
}

五大内置Interceptor

okhttp中五大Interceptor各自分工明确,依次执行,整个网络请求的过程从发起请求到最终获取响应都是在这五大Interceptor中完成的。

  1. RetryAndFollowUpInterceptor:重试拦截器,用于失败重连
  2. BridgeInterceptor:桥接拦截器,主要初始化请求头
  3. CacheInterceptor:本地缓存拦截器
  4. ConnectInterceptor:连接拦截器
  5. CallServerInterceptor:服务器交互拦截器

在对这五大拦截器做详细分析时,只针对拦截器的intercept()方法的功能出发,如果涉及到一些其他类及方法只做一个说明,不会做深入分析。

RetryAndFollowUpInterceptor

该拦截器的功能如下:

  1. 创建一个StreamAllocation对象,这个会在后面的ConnectInterceptor使用到
  2. 调用RealInterceptorChain.proceed()方法获取Response
  3. 根据Response状态码判断是否需要重连
  4. 将Response返回给上一个拦截器

intercept方法

    @Override 
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        RealInterceptorChain realChain = (RealInterceptorChain) chain;
        Call call = realChain.call();
        EventListener eventListener = realChain.eventListener();

        // StreamAllocation用于获取服务端的连接,并获取服务端的输入输出流,在后面的ConnectInterceptor拦截器中会使用到
        StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
                createAddress(request.url()), call, eventListener, callStackTrace);
        this.streamAllocation = streamAllocation;

        while (true) { // 这是一个死循环,实现请求重试

            Response response;
            boolean releaseConnection = true;
            try {
                // 获取网络响应
                response = realChain.proceed(request, streamAllocation, null, null);
                releaseConnection = false;
            }

            // followUpRequest方法根据获取响应的响应码,判断是否需要重连
            Request followUp = followUpRequest(response, streamAllocation.route());


            if (followUp == null) {
                if (!forWebSocket) {
                    streamAllocation.release();
                }
                // 获取正常网络请求时,会在这里返回响应,这个网络请求流程结束
                return response;
            }

            // 当重现次数大于MAX_FOLLOW_UPS(默认20),就会结束循环,不在重连
            if (++followUpCount > MAX_FOLLOW_UPS) {
                streamAllocation.release();
                throw new ProtocolException("Too many follow-up requests: " + followUpCount);
            }
        }
    }

BridgeInterceptor

该拦截器的功能如下:

  1. 为Request实例添加头部信息,将该Request构造成一个标准的能够被服务器识别的请求。
  2. 调用RealInterceptorChain.proceed()方法获取Response
  3. 如果Response支持GZIP,那么会对该Response进行处理
  4. 将Response返回给上一个拦截器

这个拦截器功能比较简单这里就不针对代码做说明了。

CacheInterceptor

该拦截器的功能如下:

  1. 根据请求获取缓存响应结果
  2. 调用RealInterceptorChain.proceed()方法获取Response
  3. 将从网路上获取的Response缓存到本地
  4. 将Response返回给上一个拦截器

Okhttp中缓存策略主要使用DiskLruCache。

设置缓存

在使用okhttp访问网络请求时,需要做如下配置:

设置缓存路径及大小


OkHttpClient client = new OkHttpClient.Builder()
        .cache(new Cache(new File("cacheDir"), 1024*1024*10))
        .build();

设置Request请求缓存策略:

Request request = new Request.Builder()
        .cacheControl(CacheControl.FORCE_CACHE)// 从缓存中获取
        .cacheControl(CacheControl.FORCE_NETWORK)// 从网络中获取
        .url("url")
        .build();

CacheControl.FORCE_CACHE和CacheControl.FORCE_NETWORK分别表示从缓存中获取和从网络中获取。

同时在CacheInterceptor中需要注意的时:okhttp只会将GET请求的响应缓存到本地。
这个可以查看Cache.put方法(这个方法最终会在intercept()方法中被调用):

  @Nullable CacheRequest put(Response response) {
    String requestMethod = response.request().method();

    // 省略部分代码

    if (!requestMethod.equals("GET")) {
      // Don't cache non-GET responses. We're technically allowed to cache
      // HEAD requests and some POST requests, but the complexity of doing
      // so is high and the benefit is low.
      return null;
    }
     // 省略部分代码
}

intercept方法

@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
        // 1、首先获取该Request的缓存响应,以作为备用(不管该request是否需要使用响应)
        Response cacheCandidate = cache != null
                ? cache.get(chain.request())
                : null;

        long now = System.currentTimeMillis();

        // 3、创建一个缓存策略,决定是否使用网络响应还是缓存响应
        // CacheStrategy中涉及到多种情况下关于是否使用缓存的判断,这里就不多描述了
        CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
        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.
        }

        // 被禁止从网络上获取数据,同时没有获取到缓存,这个时候创建一个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 (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.
        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();

                // 更新缓存数据
                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.
                // 将网络响应缓存到cache中
                CacheRequest cacheRequest = cache.put(response);
                return cacheWritingResponse(cacheRequest, response);
            }

            if (HttpMethod.invalidatesCache(networkRequest.method())) {
                try {
                    cache.remove(networkRequest);
                } catch (IOException ignored) {
                    // The cache cannot be written.
                }
            }
        }

        return response;
}

ConnectInterceptor

该拦截器的功能:

  1. 初始化网络连接的RealConnection对象,以及对网络请求做编解码的HttpCodec对象
  2. 调用RealInterceptorChain.proceed方法获取

总结下来就一句话,打开一个到目标服务器的连接。

其实这个连接器要处理的逻辑特别多,包括连接池ConnectionPool的一些操作,这里只针对拦截器

intercept方法

@Override
public Response intercept(Chain chain) throws IOException {
        RealInterceptorChain realChain = (RealInterceptorChain) chain;
        Request request = realChain.request();
        // 1、获取streamAllocation,这个streamAllocation是在RetryAndFollowUpInterceptor初始化的
        StreamAllocation streamAllocation = realChain.streamAllocation();

        // We need the network to satisfy this request. Possibly for validating a conditional GET.
        boolean doExtensiveHealthChecks = !request.method().equals("GET");
        // 2、创建HttpCodec对象,该对象的主要作用就是编解码请求响应
        HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
        // 3、获取RealConnection对象,该对象的主要作用就是实现网络IO流输入输出的,这个对象在newStream()方法中初始化
        RealConnection connection = streamAllocation.connection();
        // 4、调用RealInterceptorChain方法获取响应
        return realChain.proceed(request, streamAllocation, httpCodec, connection);
}

这里我们看下通过streamAllocation.newStream()方法创建HttpCodec对象的具体细节,如下:

    public HttpCodec newStream(
            OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
        int connectTimeout = chain.connectTimeoutMillis();
        int readTimeout = chain.readTimeoutMillis();
        int writeTimeout = chain.writeTimeoutMillis();
        int pingIntervalMillis = client.pingIntervalMillis();
        boolean connectionRetryEnabled = client.retryOnConnectionFailure();

        try {
            // 1、通过findHealthyConnection()方法获取一个RealConnection对象
            RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
                    writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
            // 2、创建HttpCodec对象并返回
            HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

            synchronized (connectionPool) {
                codec = resultCodec;
                return resultCodec;
            }
        } catch (IOException e) {
            throw new RouteException(e);
        }
    }


    /**
     * 寻找一个健康的连接
     */
    private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
                                                 int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
                                                 boolean doExtensiveHealthChecks) throws IOException {

        // 3、通过一个死循环,直到找到一个连接才跳出该循环
        while (true) {
            //4、通过findConnection创建一个连接
            RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
                    pingIntervalMillis, connectionRetryEnabled);

            // If this is a brand new connection, we can skip the extensive health checks.
            synchronized (connectionPool) {
                if (candidate.successCount == 0) {
                    return candidate;
                }
            }

            // 5、判断如果连接不是健康的就继续寻找下一个连接
            if (!candidate.isHealthy(doExtensiveHealthChecks)) {
                noNewStreams();
                continue;
            }

            return candidate;
        }
    }

    /**
     * Returns a connection to host a new stream. This prefers the existing connection if it exists,
     * then the pool, finally building a new connection.
     */
    private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
                                          int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {

        // 省略部分代码

        synchronized (connectionPool) {

            if (result == null) {
                // 6、从连接池中获取一个连接
                Internal.instance.get(connectionPool, address, this, null);
                if (connection != null) {
                    foundPooledConnection = true;
                    result = connection;
                } else {
                    selectedRoute = route;
                }
            }
        }
        // 省略部分代码


        // 开始执行一个连接
        // Do TCP + TLS handshakes. This is a blocking operation.
        result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
                connectionRetryEnabled, call, eventListener);
        routeDatabase().connected(result.route());

        Socket socket = null;
        synchronized (connectionPool) {
            reportedAcquired = true;

            // 缓存连接
            Internal.instance.put(connectionPool, result);

            // If another multiplexed connection to the same address was created concurrently, then
            // release this connection and acquire that one.
            if (result.isMultiplexed()) {
                socket = Internal.instance.deduplicate(connectionPool, address, this);
                result = connection;
            }
        }
        closeQuietly(socket);

        eventListener.connectionAcquired(call, result);
        return result;
}

CallServerInterceptor

该拦截器的功能如下:

  1. 向服务器发起一个Request
  2. 从服务器获取一个Response,并返回Response

HttpCodec这个类非常关键,在该拦截器中所有的流程都是基于该类。详细流程如下:

  1. HttpCodec向socket写入请求头信息
  2. HttpCodec向socket写入请求体信息
  3. httpCodec调用finishRequest()方法,表示一个请求发送完成
  4. HttpCodec读取响应头信息
  5. HttpCodec读取请求体信息

intercept方法

@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    // httpCodec:编码请求,解码响应,输入输出流
    HttpCodec httpCodec = realChain.httpStream();
    // 用于分配网络所需要的资源
    StreamAllocation streamAllocation = realChain.streamAllocation();
    // 网络连接
    RealConnection connection = (RealConnection) realChain.connection();
    // 网络请求
    Request request = realChain.request();

    long sentRequestMillis = System.currentTimeMillis();

    realChain.eventListener().requestHeadersStart(realChain.call());
    // 1、向socket写入请求头信息
    httpCodec.writeRequestHeaders(request);
    realChain.eventListener().requestHeadersEnd(realChain.call(), request);

    Response.Builder responseBuilder = null;
    // 判断是否可以发送带有请求体的信息
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
        // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
        // Continue" response before transmitting the request body. If we don't get that, return
        // what we did get (such as a 4xx response) without ever transmitting the request body.

        // 于客户端在发送 post 数据给服务器时,征询服务器情况,看服务器是否处理 post 的数据,如果不处理,客户端则不上传 post 是数据,反之则上传。
        // 在实际应用中,通过 post 上传大数据时,才会使用到 100-continue 协议
        if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
            httpCodec.flushRequest();
            realChain.eventListener().responseHeadersStart(realChain.call());
            responseBuilder = httpCodec.readResponseHeaders(true);
        }

        if (responseBuilder == null) {
            // Write the request body if the "Expect: 100-continue" expectation was met.
            realChain.eventListener().requestBodyStart(realChain.call());
            long contentLength = request.body().contentLength();
            CallServerInterceptor.CountingSink requestBodyOut =
                    new CallServerInterceptor.CountingSink(httpCodec.createRequestBody(request, contentLength));
            BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
            // 2、向socket写入请求体信息
            request.body().writeTo(bufferedRequestBody);
            bufferedRequestBody.close();
            realChain.eventListener()
                    .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
        } else if (!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.
            streamAllocation.noNewStreams();
        }
    }

    // 结束写入请求信息
    httpCodec.finishRequest();

    // 开始读取响应
    if (responseBuilder == null) {
        realChain.eventListener().responseHeadersStart(realChain.call());
        // 读取响应头信息
        responseBuilder = httpCodec.readResponseHeaders(false);
    }

    // 初始化响应配置
    Response response = responseBuilder
            .request(request)
            .handshake(streamAllocation.connection().handshake())
            .sentRequestAtMillis(sentRequestMillis)
            .receivedResponseAtMillis(System.currentTimeMillis())
            .build();

    // 读取响应头
    int code = response.code();
    if (code == 100) {
        // server sent a 100-continue even though we did not request one.
        // try again to read the actual response
        responseBuilder = httpCodec.readResponseHeaders(false);

        response = responseBuilder
                .request(request)
                .handshake(streamAllocation.connection().handshake())
                .sentRequestAtMillis(sentRequestMillis)
                .receivedResponseAtMillis(System.currentTimeMillis())
                .build();

        code = response.code();
    }

    realChain.eventListener()
            .responseHeadersEnd(realChain.call(), 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(httpCodec.openResponseBody(response))
                .build();
    }

    if ("close".equalsIgnoreCase(response.request().header("Connection"))
            || "close".equalsIgnoreCase(response.header("Connection"))) {
        // 关闭流操作。
        streamAllocation.noNewStreams();
    }

    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
        throw new ProtocolException(
                "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    // 最后返回响应,这样整个网络请求到这里就结束了
    return response;
}

总结

这样,关于Okhttp中五大内置拦截器的功能就分析到这里了。
由于篇幅问题,本章节只是对这些拦截器的工作原理做了简单的分析,其中还有一些重要的内容没有提及到,包括:

  • Cache及DiskLruCahce如何实现okhttp中的缓存机制
  • StreamAllocation的工作原理
  • 连接池ConnectionPool的工作原理
  • HttpCodec的底层实现,如何通过Socket同服务器交互

这些内容将会在以后进行补充。

Android 第三方框架:网络:OkHttp:源码分析:拦截器 将传入的Interceptor对象放入OkHttpClient.interceptors中OkHttpClient.interceptors最早添加到Interceptor的责任链中被添加到所有系统定义的Interceptor之前。 阅读详情

相关推荐

MDK开发篇:基于立创逻辑派FPGA-G1与Cortex-M1软核的按键控制LED实战

本文详细介绍了基于立创逻辑派FPGA-G1开发板与高云Cortex-M1软核的完整开发流程。通过Gowin云源软件配置软核系统与GPIO外设,并在Keil MDK环境中编写按键控制LED的C程序,最终实现程序固化与上电自启动。教程涵盖硬件配置、软件开发、在线调试与固化部署全步骤,帮助STM32开发者快速上手FPGA软核开发。

weixin_42103128的博客 148

java.net.ProtocolException:Too many follow-up requests:21

这几天在android 上用jsoup爬取网页数据,先使用Okhttp获取到目标网页html,然后使用jsoup解析网页,结果昨天还好好地,今天早上就出了问题,说是java.net.ProtocolException:Too many follow-up requests:21 ,查了一顿 说是目标网页做了什么处理,但是我单用jsoup就可以解析出来,这个问题先遗留着吧

qingdaohaoyunpeng的专栏 2万+

PICO+Unity MR空间网格开发实战:从零构建透视交互场景

本文详细介绍了使用PICO 4 Ultra与Unity引擎进行MR空间网格开发的完整实战流程。从环境准备、项目初始化、核心组件配置,到空间网格的生成、可视化与物理交互实现,手把手教你构建一个能识别真实环境并发射虚拟小球的透视交互场景。文章重点解析了PICO SDK中空间网格功能的关键配置与优化技巧,并提供了真机部署与调试的实用指南。

e3f4g5的博客 490

OKHttp系列八、RetryAndFollowUpInterceptor重定向拦截器

主要作用——在指定的条件下失败后重连。 RetryAndFollowUpInterceptor有两个核心的方法,一个是Response intercept(Chain chain),另一个是intercept调用的Request followUpRequest(Response userResponse, Route route)方法,这两个方法我们后面慢慢梳理。 Intercept方法很长,源码...

haoyuegongzi的博客 957

DSP28335做FFT傅里叶变换

1. 看了一下例程,居然没有FFT的例程,难道这个DSP28335不能做FFT吗?对了C2000系列是有C2000 ware这个库的。方便很多,不过目前不确定在C5000上运行的FFT能直接迁移到DSP28335上行不?测试了一下,是可以的,因为都是用的math.h,下面的一些正弦函数基本一样的 2. 硬件简介,其中DSP28335和TLV320AIC23B之间使用I2C接口进行寄存器配置,...

weixin_30390075的博客 3331

PPO(Proximal Policy Optimization)算法原理及实现,详解近端策略优化

接着上面的讲,PG方法一个很大的缺点就是参数更新慢,因为我们每更新一次参数都需要进行重新的采样,这其实是中on-policy的策略,即我们想要训练的agent和与环境进行交互的agent是同一个agent;与之对应的就是off-policy的策略,即想要训练的agent和与环境进行交互的agent不是同一个agent,简单来说,就是拿别人的经验来训练自己。举个下棋的例子,如果你是通过自己下棋来不断提升自己的棋艺,那么就是on-policy的,如果是通过看别人下棋来提升自己,那么就是off-policy的。

weixin_41106546的博客 9万+

Android 网络编程之OkHttp源码解析

前言:OkHttp框架是Android的网络请求框架,无数的项目都在使用着这个框架,重要性不言而喻; 本文会将OKHTTP的源码进行拆解,每个部分来单独学习,由简入深,循序渐进,篇幅较长,建议收藏,慢慢观看,如果觉得内容不错的话,点赞关注来一波,感谢! 源码基于okhttp3 java版本:3.14.9 OkHttp3的简单使用: public void request() { String url = "http://wwww.baidu.com"; OkH.

十年磨一剑 706

nCode:GlyphWorks案例教程一

nCode GlyphWorks案例一Comparing Frequency Content of Two Different Tests案例文件目标配置选择数据 Comparing Frequency Content of Two Different Tests 在本案例中,我们将比较来自两个不同测试的六个加速计通道的频率内容。将三轴加速度计放置在车辆的左前角和右前角,然后分别在两个不同的试验路面上行驶12分钟以上。 案例文件 此案例的文件(采用nCode DAC格式)位于nCode安装目录下的\demo

追月行的博客 1万+

根据Interceptor 分析 OkHttp(一)

Interceptor OkHttp在介绍Interceptor前需要理解几个概念Requests每个HTTP请求都包含一个URL,一个method(比如GET/POST),还有一系列的headers。Requests 还可能包含一个body:一个指定content type的data stream。ResponsesResponses是通过一个code(比如200代表请求成功、4

大神了没 1028

数字设计实验:RISC-V指令单周期CPU

本文旨在介绍RISC-V指令单周期CPU的设计思路和仿真实现(含代码)

2401_83740084的博客 2713

CAPL通过lookupSignal和DBLookup获取DBC信号的属性信息

CAPL通过lookupSignal和DBLookup获取DBC信号的属性信息以及一些相关问题

天赐好车的博客 2889

OkHttp3源码分析之拦截器Interceptor

前言 在上一篇博客中,我们从源码分析了,一次异步网络请求的整个大概表面的流程,但是涉及到某些具体的内容呢,就直接带过了。本篇文章我们就先来了解一下在发起一次网络请求时,OkHttp是怎么发起请求获取响应的。这里边就涉及到OkHttp的一个很棒的设计——拦截器Interceptor。 分析 源码基于最新的版本:3.10.0。 还记得上一篇博客中一次异步任务中,到最后一步执行的代码吗? ...

苦逼的程序猿☺ 1339

Unity 工具 之 ZXing QRCode 二维码的生成和识别(可设置生成不同颜色的/带图标的二维码)

Unity 基础 之 ZXing QRCode 二维码的生成和识别(可设置生成不同颜色的/带图标的二维码) 目录 Unity 基础 之 ZXing QRCode 二维码的生成和识别(可设置生成不同颜色的/带图标的二维码) 一、简单介绍 二、实现原理 三、注意实现 四、效果预览 五、ZXing.net ( zxing.unity.dll ) 插件下载 六、实现步骤 七、关键代码 一、简单介绍 Unity 开发中,自己整理的一些游戏开发可能用到的功能,整理归档,方便游...

仙魁XAN 9824

深入解析OkHttp3

OkHttp是一个精巧的网络请求库,有如下特性: 1)支持http2,对一台机器的所有请求共享同一个socket 2)内置连接池,支持连接复用,减少延迟 3)支持透明的gzip压缩响应体 4)通过缓存避免重复的请求 5)请求失败时自动重试主机的其他ip,自动重定向 6)好用的API其本身就是一个很强大的库,再加上Retrofit2、Picasso的这一套组合拳,使其愈发的受到开发者的关注

一个码农的博客 6万+

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

设计模式:责任链模式

设计模式:责任链模式

RQ997832的专栏 2810

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

源码解读系列(三)网络框架之OkHttp3(上)--请求流程

1、OkHttp3的基本使用 //1.1 GET请求 public void get() { //1、构建request Request request = new Request.Builder() .url("https://blog.youkuaiyun.com/baidu_31093133") .g...

秦时明月 601

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

图解okhttp3源码解析

okhttp3应该是android开发中比较流行的网络框架了,从Android 4.4开始google已经开始将源码中的HttpURLConnection替换为OkHttp,而在Android 6.0之后的SDK中google更是移除了对于HttpClient的支持,而现在流行的Retrofit同样是使用OkHttp进行再次封装而来的。 本文自对okhttp3进行源码流程进行解析,并不介绍...

zcv5and的博客 367

okhttp3源码分析:架构全面解析

一、摘要 okhttp3在android应用开发中是现今最牛(哔)的网络交易框架,不接受反驳。 二、okhttp3源码地址 okhttp3框架源码地址:https://github.com/square/okhttp 三、okhttp3简单使用 OkHttpClient client = new OkHttpClient.Builder().build(); Requ...

android开发笔记 1636

关于Okhttp3(三)--请求流程

前两天我们介绍了,基本使用和如何查看源码,今天我们正式进入源码分析流程。 流程图 首先我们先看一看它的请求流程,在Okhttp3中请求是基于拦截器原理,源代码如下: 源码路径:okhttp3/RealCall.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 ...

Red风信子的博客 1431

Android网络框架——OKHttp拦截器

一、Interceptor 的作用 二、Interceptor 的类型 1. Application Interceptor(应用拦截器) 2. Network Interceptor(网络拦截器) 3.源码解读 三、Interceptor使用

闲暇部落 2179

okhttp3源码分析之拦截器

okhttp3源码分析之拦截器 前言 上一篇文章:okhttp3源码分析之请求流程 本篇文章继续通过源码来探讨okhttp的另外一个重要知识点:拦截器,在上一篇文章我们知道,在请求发送到服务器之前有一系列的拦截器对请求做了处理后才发送出去,在服务器返回响应之后,同样的有一系列拦截器对响应做了处理后才返回给发起请求的调用者,可见,拦截器是okhttp的一个重要的核心功能,在分析各个拦截器功能的同...

Kelvin的专栏 328

OkHttp3初探:拦截器源码分析

目录 一、OkHttp3的拦截器 二、Interceptor接口 三、RetryAndFollowUpInterceptor 三、BridgeInterceptor 四、CacheInterceptor 五、ConnectInterceptor 六、CallServerInterceptor 七、addInterceptor与addNetworkInterceptor的区别 八、...

ozqowen的博客 369

详细理解OkHttp3中的拦截器4

拦截器是OkHttp3中的一个重要机制,它允许开发者在HTTP请求和响应的各个阶段进行自定义处理。通过拦截器,可以实现如日志记录、请求重试、响应缓存等功能。

jiet_h的博客 2152

OkHttp3使用(三)-Interceptor

在上一篇文章中,我们介绍了OkHttp的基本用法。本篇文章,我们继续介绍其Interceptor的用法。在OkHttp框架中,Interceptor算是其强大的原因所在。我们平时很多功能(如下载进度监听、缓存策略设置、日志打印等)都需要通过Interceptor来实现。 Interceptor介绍 我们先看下下Interceptor这个类的源码: public interface Intercep...

VictorCatFish的博客 4452

okhttp3-源码分析(2) 拦截器链的实现

拦截器链(Interceptor)应该是okhttp框架的核心之一。 责任链模式 okhttp的请求拦截器的实现是一种典型的责任链模式,在okhttp中有一个Interceptor接口,默认的拦截器和我们自定义的拦截器都要实现这个接口。Interceptor接口内部还有一个内部接口Chain。在okhttp中一般使用的是他的具体实现类RealInterceptorChain 下面Interceptor接口的部分代码: public interface Interceptor { //chain里面包含re

m0_50930469的博客 907

OkHttp3源码分析(下):请求实现 Interceptor

1.请求的具体实现 调用入口 RealCall#getResponseWithInterceptorChain() 收集所有的Interceptor 从第0个Interceptor开始,逐个调用chain.proceed 最后将结果Response返回给调用者 Response getResponseWithInterceptorChain() throws IOException { ...

wei.zheng BLOG 268
上一篇: Okhttp3源码分析(2) Dispatcher分发器
下一篇: Retrofit2源码分析
Kerwin zhang
博客等级 码龄12年 21粉丝 28原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值