[关闭]
@Awille 2019-01-05T10:43:48.000000Z 字数 10293 阅读 103

okhttp中的拦截器链

okhttp android 网络请求


序言

在项目开发当中,涉及到对http的request请求加签以及对返回的reponse进行验签的需求,项目中这两项的实现都是通过在okhttpClient的builder建造者中添加拦截器实现的,所以希望对okhttp的拦截器机制有了解。

1、okhttp拦截器链设计模式

okhttp的拦截链设计采用的是责任链模式。
在责任链模式当中,很多对象由 每个对象对其下家的引用连接起来而形成一条处理请求的链。当客户端请求到达时,并不知道在该条链路上是哪一个对象处理了请求,这样的好处是可以对客户端过来的请求做动态的处理
a.纯责任链模式:当请求到达一个类时要么处理完,要么扔给下一个对象
b.不纯的责任链模式:当请求到达时,该类处理一部分(可以配置一些参数,适配一下请求),然后扔给下一个。

2、okhttp拦截器链解析

首先我们在源码中可以看到:
RealCall#execute()方法当中,获取最终的返回结果是通过该行函数来实现的:

Response result = 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());
    return chain.proceed(originalRequest);
  }

从上面的函数可以看到,首先创建了一个数组存储了以下的拦截链:
a、interceptors.addAll(client.interceptors());这里是添加设置okhttpclient时设置的interceptors,我们做的一些自定义个拦截器也是通过这里添加上去的(比如我们需求要我们做的设置的加签验签的拦截器)
b、retryAndFollowUpInterceptor 负责失败重试以及重定向的拦截器
c、BridgeInterceptor 负责把用户构造的请求转换为发送到服务器的请求 以及把 服务器的reponse转换成为用户友好的response
d、CacheInterceptor 负责读取缓存,更新缓存
e、ConnectInterceptor 负责和服务器构建链接
f、client.networkInterceptors() 添加构建okhttpclient时构建的networkInterceptors
g、CallServerInterceptor 负责向服务器发送请求数据、从服务器读取响应数据的

最后的语句:

return chain.proceed(originalRequest);

这里就是开启链式调用的。

2.1 对Interceptor接口的解析

上面所有的拦截链都是继承Interceptor接口来实现的,我们可以来看下这个接口:

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;
  interface Chain {
    Request request();
    Response proceed(Request request) throws IOException;
  }
}

2.1.1 Interceptor接口

Interceptor接口中的intercept方法是实际处理请求的具体实现,在拦截链当中,每一个实现该接口的对象负责处理reponse的一部分

2.1.2 Chain接口

Chain接口中的接口负责执行intercept方法,request()方法简单明了,及是返回当前的request请求。
而proceed方法的解析,我们可以看一下框架中使用的最多的Chain接口实现类RealInterceptorChain都干了些什么事情。
首先看到重写的proceed方法:

@Override 
public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
}

proceed方法的实现:

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
    //interceptors是一个拦截器组成的list
    if (index >= interceptors.size()) throw new AssertionError();
    //这个int参数是用来做标记的,确保拦截器链上的每个拦截器只被调用一次
    calls++;
    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {
    throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
        + " must call proceed() exactly once");
    }
    //调用拦截器链的下一个拦截器,可以观察到(index + 1)这个参数,这个参数是用来推动拦截器往下移动的
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
    connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
    writeTimeout);
    //这里index的作用可以看到,就是获取拦截器链上的相应拦截器的
    Interceptor interceptor = interceptors.get(index);
    //调用intercept方法获得reponse,传入的参数为指向下一个节点的chain对象
    Response response = interceptor.intercept(next);
    //下面都是省略一些对返回的reponse的参数做合法性判断的
    ...
    return response;
}

可以看到,这个proceedfang方法主要职责就是逐个调用拦截器的拦截链,Chain接口的两个方法一个是返回request对象,一个是调用Interceptor接口中的intercept,这个intercept方法吧Chain对象当做参数传递,实现request跟reponse对象在拦截器链中的层层传递。

2.3 RetryAndFollowUpInterceptor 负责失败重试以及重定向的拦截器

@Override 
public Response intercept(Chain chain) throws IOException {
    //根据chain获得传递的request
    Request request = chain.request();
    //chain对象转换
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    //call 对象获取
    Call call = realChain.call();
    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
        //... 省略判断是否取消的相关操作
        Response response;
        boolean releaseConnection = true;
        try {
            //调用拦截器的方法
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
        } catch (RouteException e) {
            //情况一:路由链接失败 request请求未发送
            //判断是否可以修复
        if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
            throw e.getFirstConnectException();
        }
        releaseConnection = false;
        //重试
        continue;
        } catch (IOException e) {
        // 情况二:服务器链接失败,
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        //recover尝试修复该连接,如果是不可修复的连接,直接抛出异常,由于后面没有再
        //捕获异常的catch了,那么函数直接停止运行
        if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
        releaseConnection = false;
        //重连  判断为可修复
        continue;
        } finally {
        // 在finalliy中判断是否链接已经释放,如果释放了就释放资源
        if (releaseConnection) {
            streamAllocation.streamFailed(null);
            streamAllocation.release(true);
        }
        }
        // 如果上次尝试连的reponse,这一类的reponse是没有body的
        // priorResponse是记录上次尝试的reponse,在后面的closeQuietly函数中把body设为null
        if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
        }
        Request followUp;
        //followUpRequest该函数的作用
        //这个函数如判断根据reponse返回重新构造request,如果是正常reponse会构造request为null,
        //如果是异常的reponse,比如需要重定向,会自动构建重定向的request
        //当然如果是404等错误,也是直接构造request为null。
        try {
        followUp = followUpRequest(response, streamAllocation.route());
        } catch (IOException e) {
        streamAllocation.release(true);
        throw e;
        }
        //followUp为空,要么是成功的返回,
        //要么是404等不可通过修改request参数来修复的错误
        if (followUp == null) {
        streamAllocation.release(true);
        return response;
        }
        //这里就是priorResponse的body为null的原因
        closeQuietly(response.body());
        //超过最大尝试重连次数,直接就抛出异常退出
        if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release(true);
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
        }
        //... 这里省略了一些异常情况的判断
        request = followUp;
        priorResponse = response;
    }
}

可以看到这个RetryAndFollowUpInterceptor在某些网络请求失败的情况下帮我们做尝试重连操作,甚至完善到了根据返回的reponse的状态码以及返回信息重新构建request请求去进行网络请求(比如重定向这种操作)。

2.4 BridgeInterceptor 实现应用(可以理解为我们自己的app)request到实际的网络的request,以及网络的response到应用的reponse转换

同样是查看intercept方法:

@Override 
public Response intercept(Chain chain) throws IOException {
    //这是我们应用上构建的request
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();
    RequestBody body = userRequest.body();
    //类型转换 一些我们没有赋值的字段,比如Host、Connection中的字段
    //另外header中Connection字段的值如我们没有设置,这里默认会使用Keep-Alive这个值
    //省略header字段的补充
    //Accept-Encoding字段如果没设置,会设为为gzip
    //添加cookie
    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
    requestBuilder.header("Cookie", cookieHeader(cookies));
    }
    //代理
    if (userRequest.header("User-Agent") == null) {
    requestBuilder.header("User-Agent", Version.userAgent());
    }
    //获得reponse,这里跟之前一样,这种调用方法可以理解为递归调用,
    //这里得到的是调用以后各个拦截器得到的返回
    Response networkResponse = chain.proceed(requestBuilder.build());
    //network reponse到 applicaition reponse的转换
    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);
    //... 省略帮我们做解压等操作,还有剔除一些我们应用上不需要的header字段
    return responseBuilder.build();
}

通过这个拦截器的实现,我们可以看到一个拦截器大概做的事情:
a、首先通过传入的chain得到request
b、对request进行我们需要的操作(比如加签)
c、通过chain调用下一个拦截器的intercept方法(递归调用)得到返回的reponse
d、对reponse记性我们需要的操作(比如验签名)
e、直接把reponse返回回去

2.5 CacheInterceptor 负责读取缓存,更新缓存

@Override 
public Response intercept(Chain chain) throws IOException {
    //判断是否有上一个传入request请求的缓存
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;
    long now = System.currentTimeMillis();
    //获得当前的缓存策略
    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.
    }
    //如果不允许使用缓存
    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();
    }
    // 如果没有构造出networkrequest,说明不要网络,直接返回缓存结果
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
    Response networkResponse = null;
    try {
        //继续拦截链的调用
      networkResponse = chain.proceed(networkRequest);
    } finally {
      ...
    }
    // 根据返回的reponse的相关字段对已有缓存进行处理
    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());
      }
    }
    ...
    return response;
  }

主要是做以下事情:
首先,根据request来判断cache中是否有缓存的response,如果有,得到这个response,然后进行判断当前response是否有效,没有将cacheCandate赋值为空。根据request判断缓存的策略,是否要使用了网络,缓存 或两者都使用调用下一个拦截器,决定从网络上来得到response如果本地已经存在cacheResponse,那么让它和网络得到的networkResponse做比较,决定是否来更新缓存的cacheResponse

2.6 ConnectInterceptor 负责和服务器构建链接

 @Override 
  public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }

通过创建HttpCodec对象进行连接

2.7 CallServerInterceptor 负责向服务器发送请求数据、从服务器读取响应数据

这个拦截器首先向服务器发起连接,然后根据返回的reponse的返回码进行判断,然后直接返回。
这个拦截器应该是okhttp拦截器链的最后一个拦截器,没有看到其调用传入的chain对象的proceed方法。它返回的reponse是在从服务器获得的reponse基础上做了返回码判断二次构造后返回回来的。

3、加签与验签的拦截器的大概实现模式:

public class MyInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        //加签 对request处理
        Response response = chain.proceed(request);
        //验签 对reponse操作 打点也可以放在这里
        return reponse;
    }
}

在okhttpclient.builder创建时,将拦截器加上去

OkHttpClient.Builder builder = new OkHttpClient.Builder();
            builder.addInterceptor(new MyInterceptor());

如果使用retrofit,可以创建时把整个OkHttpClient加进去:

mRetrofit = new Retrofit.Builder()
                .baseUrl(url)
                .addCallAdapterFactory(new HttpCallAdapterFactory())
                .addConverterFactory(GsonConverterFactory.create())
                .client(builder.build())
                .build();
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注