IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> OKHTTP深入浅出(五)----拦截器(2)ridgeInterceptor与CacheInterceptor -> 正文阅读

[网络协议]OKHTTP深入浅出(五)----拦截器(2)ridgeInterceptor与CacheInterceptor

上一篇文章我们说了OKHTTP的重定向和跟进拦截器OKHTTP深入浅出(四)----拦截器(1)RetryAndFollowUpInterceptor_王胖子总叫我减肥的博客-CSDN博客

下面我们来说说桥拦截器ridgeInterceptor和缓存拦截器CacheInterceptor

1、桥拦截器ridgeInterceptor

? ? ?桥拦截器相当于应用层和网络层之间的桥梁,相当于 在 请求发起端 和 网络执行端 架起一座桥,把应用层发出的请求(请求发送端)?变为 网络层认识的请求(网络执行端),把网络层执行后的响应 变为 应用层可以直接使用的结果。

public final class BridgeInterceptor implements Interceptor {
  //Cookie 管理器,初始化okhttpClient的时候创建的
  private final CookieJar cookieJar;

  public BridgeInterceptor(CookieJar cookieJar) {
    this.cookieJar = cookieJar;
  }

  @Override public Response intercept(Chain chain) throws IOException {
    //获取请求发送端的请求
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();
    
    // 封装处理了contentType、contentLength、Transfer-Encoding、
    // Host、Connection、Accept-Encoding、Cookie、User-Agent等请求头
    RequestBody body = userRequest.body();
    if (body != null) {
      MediaType contentType = body.contentType();
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }

      long contentLength = body.contentLength();
      if (contentLength != -1) {
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }
    }

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

    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection", "Keep-Alive");
    }

    // 如果服务器支出Gzip压缩,客户端不用设置,OKhttp会自动帮我们开启Gzip压缩和解压
    // 如果客户端设置了Gzip,那就需要自己解压服务器返回的数据
    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }
    
    // 从CookieJar中获取 cookies,添加到header
    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());
    }

    // 把处理好的请求往下传递,执行后续的拦截器的逻辑
    Response networkResponse = chain.proceed(requestBuilder.build());
    // 获取networkResponse中的header的“set-cookie” ,存入到Cookiejar
    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
    // 获取networkResponse的Builder
    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);
    
    //处理返回的Response的"Content-Encoding"、"Content-Length"、"Content-Type"等返回头
//如果我们没有手动添加"Accept-Encoding: gzip",这里会创建能自动解压的
//responseBody-GzipSource
    if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
      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)));
    }

    //返回处理好的Response
    return responseBuilder.build();
  }

  /** Returns a 'Cookie' HTTP request header with all cookies, like {@code a=b; c=d}. */
  private String cookieHeader(List<Cookie> cookies) {
    StringBuilder cookieHeader = new StringBuilder();
    for (int i = 0, size = cookies.size(); i < size; i++) {
      if (i > 0) {
        cookieHeader.append("; ");
      }
      Cookie cookie = cookies.get(i);
      cookieHeader.append(cookie.name()).append('=').append(cookie.value());
    }
    return cookieHeader.toString();
  }
}

? ? ? 首先在Response networkResponse = chain.proceed(requestBuilder.build());真正的请求执行之前,获取了请求的Builder,对请求添加了header:“Content-Type”、“Content-Length” 或 “Transfer-Encoding”、“Host”、“Connection”、“Accept-Encoding”、“Cookie”、“User-Agent”,将请求转换成网络层真正可执行的请求。其中,注意到,默认是没有cookie处理的,需要我们在初始化OkHttpClient的时候配置我们自己的CookieJar。

? ? ?chain.proceed() 执行后,先把响应header中的cookie存入cookieJar(如果有),然后如果没有手动添加请求header “Accept-Encoding: gzip”,那么会通过 创建能自动解压的responseBody——GzipSource,接着构建新的response返回。
?

2、缓存拦截器 CacheInterceptor

? ? ?缓存拦截器提供网络请求缓存的读取。如果每次请求都经过网络的发送和读取,会很耗时,假如一个相同的请求已经执行了一次,那么这一次我们是不是可以直接用上次的结果呢?这就是缓存拦截器的功能。

大概了解一下http的缓存

第一次请求

?第二次请求

?

public final class CacheInterceptor implements Interceptor {
  final @Nullable InternalCache cache;

  public CacheInterceptor(@Nullable InternalCache cache) {
    this.cache = cache;
  }

  @Override public Response intercept(Chain chain) throws IOException {
  // 获取候选缓存(cache不为空的情况)
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();

  // 1
  // 获取缓存执行的逻辑
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
 // networkRequest == null ,不使用网络
    Request networkRequest = strategy.networkRequest;
// cacheResponse == null 不使用缓存
    Response cacheResponse = strategy.cacheResponse;
  
   // 根据缓存策略更新统计指标:请求次数、使用缓存次数
    if (cache != null) {
      cache.trackResponse(strategy);
    }

  // 有缓存,但是缓存返回的cacheResponse为空。说明不能用,关闭
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

  // networkRequest == null ,不能使用网络,并且cacheResponse为null,返回504
    // If we're forbidden from using the network and the cache is insufficient, fail.
    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.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }


// 走到这里,我们已经把networkRequest == null的情况分析完了,说明往下
// networkRequest != null,但是cacheResponse可能为null,可能不为!null
// 因此下面就是没有命中缓存的情况下,进行网络请求,获取Response
//  具体的步骤(判断缓存是否命中,命中返回缓存,未命中使用网络请求Response并添加缓存)
    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) {
    // 如果缓存结果不为空,并且code为304,说明缓存没有发生变化,继续使用缓存数据
      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 {
  // 如果code不是304,说明服务端资源有更新,关闭缓存
        closeQuietly(cacheResponse.body());
      }
    }

  // 如果缓存未命中,获取网络返回的Response
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (cache != null) {
   // 如果网络请求可以缓存(请求和响应头的cache-control都不是“no -store”)
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
   // 将网络请求Response保存到缓存中
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }


// OKhttp只会对get请求进行缓存,不是就移除缓存
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;
  }

注释1,是缓存拦截器的核心,获取缓存拦截器的缓存策略。将主要的参数有当前的时间戳、请求、缓存传入到CacheStrategy.Factory中,然后调用了其中的get()方法。

CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();

先看一下CacheStrategy.Factory

   public Factory(long nowMillis, Request request, Response cacheResponse) {
      this.nowMillis = nowMillis;
      this.request = request;
      this.cacheResponse = cacheResponse;

      //解析cacheResponse,把参数赋值给自己的成员变量
      if (cacheResponse != null) {
    // 获取获选缓存的请求时间、响应时间、从header中获取过期时间、修改时间、资源标记时间(有的话)
        this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
        this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
        Headers headers = cacheResponse.headers();
        for (int i = 0, size = headers.size(); i < size; i++) {
          String fieldName = headers.name(i);
          String value = headers.value(i);
          if ("Date".equalsIgnoreCase(fieldName)) {
            servedDate = HttpDate.parse(value);
            servedDateString = value;
          } else if ("Expires".equalsIgnoreCase(fieldName)) {
            expires = HttpDate.parse(value);
          } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
            lastModified = HttpDate.parse(value);
            lastModifiedString = value;
          } else if ("ETag".equalsIgnoreCase(fieldName)) {
            etag = value;
          } else if ("Age".equalsIgnoreCase(fieldName)) {
            ageSeconds = HttpHeaders.parseSeconds(value, -1);
          }
        }
      }
    }

再看一下get()方法。使用缓存的响应,返回满足请求的策略。

 public CacheStrategy get() {
     // 获取候选缓存
      CacheStrategy candidate = getCandidate();

    // 如果候选缓存的网络请求不为空,并且请求设置了只能试用缓存,此时即使有缓存,也是过期缓存
  // 因此会 new 一个新的 CacheStrategy(缓存策略)
      if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
        // We're forbidden from using the network and the cache is insufficient.
        return new CacheStrategy(null, null);
      }

      return candidate;
    }

在深入看一下get()方法中的CacheStrategy candidate = getCandidate();

 private CacheStrategy getCandidate() {
      // 如果不使用缓存就返回一个空的response的CacheStrategy
      if (cacheResponse == null) {
        return new CacheStrategy(request, null);
      }

      // 如果是HTTPS,但是没有握手,就进行网络请求
      // Drop the cached response if it's missing a required handshake.
      if (request.isHttps() && cacheResponse.handshake() == null) {
        return new CacheStrategy(request, null);
      }

      // 不可使用缓存(请求或响应头的cache-control为“no-strore”)进行网络请求
      // If this response shouldn't have been stored, it should never be used
      // as a response source. This check should be redundant as long as the
      // persistence store is well-behaved and the rules are constant.
      if (!isCacheable(cacheResponse, request)) {
        return new CacheStrategy(request, null);
      }

   //求头的Cache-Control是no-cache 或者 请求头有"If-Modified-Since"或"If-None-Match"
  // 进行网络请求
      CacheControl requestCaching = request.cacheControl();
      if (requestCaching.noCache() || hasConditions(request)) {
        return new CacheStrategy(request, null);
      }
      
   // 强制缓存
      CacheControl responseCaching = cacheResponse.cacheControl();
      // 缓存的年龄
      long ageMillis = cacheResponseAge();
      // 缓存的有效期
      long freshMillis = computeFreshnessLifetime();
     
    // 判断强制缓存是否有效,是的话就返回缓存数据,比较请求头的有效期,取最小值
      if (requestCaching.maxAgeSeconds() != -1) {
        freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
      }

    //可接受的最小剩余有效时间(min-Fresh标识了客户端不愿意接受剩余有效期<=min-fresh的缓存)
      long minFreshMillis = 0;
      if (requestCaching.minFreshSeconds() != -1) {
        minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
      }
   
   // 可接受的最大过期时间
  // (max-stale指令标示了客户端愿意接收一个已经过期了的缓存,例如 过期了 1小时 还可以用)
      long maxStaleMillis = 0;
  //第一个判断时候要求必须去服务器验证资源
 //第二个判断,获取max-stale的值,不等于-1,说明缓存过期后,还能试用指定的时长
      if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
      }

 // 如果响应头没有要求忽略本地缓存 且 整合后的缓存年龄 小于 整合后的过期时间,那么缓存就可以用
      if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
        Response.Builder builder = cacheResponse.newBuilder();
  //  没有满足“可接受的最小 剩余有效时间”,加个110警告
        if (ageMillis + minFreshMillis >= freshMillis) {
          builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
        }
//isFreshnessLifetimeHeuristic表示没有过期时间,那么大于一天,就加个113警告
        long oneDayMillis = 24 * 60 * 60 * 1000L;
        if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
          builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
        }
        return new CacheStrategy(null, builder.build());
      }


    //缓存协议修改了request 
   //缓存是过去的,找缓存中的Etag、lastModified、serverdate
      // Find a condition to add to the request. If the condition is satisfied, the response body
      // will not be transmitted.
      String conditionName;
      String conditionValue;
      if (etag != null) {
  // etag协商缓存
        conditionName = "If-None-Match";
        conditionValue = etag;
      } else if (lastModified != null) {
   // Last-Modified协商缓存
        conditionName = "If-Modified-Since";
  // 最后修改时间
        conditionValue = lastModifiedString;
      } else if (servedDate != null) {
     // Last-Modified协商缓存
        conditionName = "If-Modified-Since";
  // 服务器最后修改时间
        conditionValue = servedDateString;
      } else {
 // 没有协商缓存,返回一个空的Response的CacheStrategy
        return new CacheStrategy(request, null); // No condition! Make a regular request.
      }
// 设置header
 
      Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
      Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

      Request conditionalRequest = request.newBuilder()
          .headers(conditionalRequestHeaders.build())
          .build();
//conditionalRequest表示 有条件的网络请求:
 //有缓存但过期了,去请求网络 询问服务端,还能不能用。能用侧返回304,不能则正常执行网路请求。
      return new CacheStrategy(conditionalRequest, cacheResponse);
    }

getCandidate()方法主要做了以下判断,获取缓存策略:

  1. 没有缓存、HTTPS没有握手、不能缓存、忽略缓存、手动配置缓存过期,直接网络请求
  2. 不属于以上情况,如果缓存没有过期,就使用缓存(可能要添加警告)
  3. 缓存过期,但响应头有Etag、last-modified、data,就添加这些响应头进行协商网络请求
  4. 如果缓存过期了,且响应头没有设置Etag,Last-Modified,Date,就进行网络请求

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-01-12 00:25:08  更:2022-01-12 00:27:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 11:37:34-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码