OkHttp-BridgeInterceptor源碼解析

BridgeInterceptor源碼解析

本文基於okhttp3.10.0

這是okhttp攔截器中第二個,作用非常簡單就是增加必要的請求頭,處理請求體。

1. 主要功能

  1. 添加請求頭
  2. Cookie管理
  3. Gzip壓縮

1.1 添加請求體

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

    RequestBody body = userRequest.body();
    if (body != null) {//body不爲空
      MediaType contentType = body.contentType();
      if (contentType != null) {//contentType不爲空
        requestBuilder.header("Content-Type", contentType.toString());//添加contentType參數
      }

      long contentLength = body.contentLength();
      if (contentLength != -1) {//body長度不爲空
        requestBuilder.header("Content-Length", Long.toString(contentLength));//添加Content-Length參數
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked");//分塊傳輸
        requestBuilder.removeHeader("Content-Length");
      }
    }

    if (userRequest.header("Host") == null) {//host爲null增加host參數
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }

    if (userRequest.header("Connection") == null) {//默認支持keep-alive
      requestBuilder.header("Connection", "Keep-Alive");
    }

    if (userRequest.header("User-Agent") == null) {//增加用戶標識信息
      requestBuilder.header("User-Agent", Version.userAgent());
    }

    Response networkResponse = chain.proceed(requestBuilder.build());//進行請求

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

    return responseBuilder.build();//返回結果
  }

剔除Cookie和Gzip代碼後BridgeInterceptor變的非常簡單,這裏我們歸納下都加了那些頭和作用

  • Content-Type:報文主體內容類型
  • Content-Length:實體主體的大小(單位:字節)
  • Transfer-Encoding:規定了傳輸報文主體時採用的編碼方式
  • Host:請求資源所在服務器,段在 HTTP/1.1 規範內是唯一一個必須被包含在請 求內的首部字段
  • Connection:管理持久連接
  • User-Agent:HTTP 客戶端程序的信息

我隨便抓了個請求,可以看下它頭部信息

1.2 Cookie管理

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

    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());//獲取本地存儲的cookie
    if (!cookies.isEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies));//cookies不等於空添加到頭部
    }

    Response networkResponse = chain.proceed(requestBuilder.build());

    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());//存儲服務端返回的coockie

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

    return responseBuilder.build();
  }

cookieJar是通過okhttpclient#cookieJar()獲取的,默認實現爲CookieJar.NO_COOKIES

public interface CookieJar {
  CookieJar NO_COOKIES = new CookieJar() {//默認實現
    @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
    }

    @Override public List<Cookie> loadForRequest(HttpUrl url) {
      return Collections.emptyList();
    }
  };

  void saveFromResponse(HttpUrl url, List<Cookie> cookies);//存儲coockie

  List<Cookie> loadForRequest(HttpUrl url);//通過url讀取cookie
}

所以在讀取的時候默認是返回一個空的list,再來看下存儲HttpHeaders#receiveHeaders()

  public static void receiveHeaders(CookieJar cookieJar, HttpUrl url, Headers headers) {
    if (cookieJar == CookieJar.NO_COOKIES) return;//默認不存儲

    List<Cookie> cookies = Cookie.parseAll(url, headers);//將headers中的cookie解析爲Cookie對象
    if (cookies.isEmpty()) return;

    cookieJar.saveFromResponse(url, cookies);//觸發存儲邏輯
  }

默認直接走到了return裏,否則通過Cookie#parseAll()解析response的header中的cookie

  public static List<Cookie> parseAll(HttpUrl url, Headers headers) {
    List<String> cookieStrings = headers.values("Set-Cookie");//其實就是取響應頭中set-cookie字段的值,判斷是cookie值轉爲cookie對象存儲在list中返回
    List<Cookie> cookies = null;

    for (int i = 0, size = cookieStrings.size(); i < size; i++) {
      Cookie cookie = Cookie.parse(url, cookieStrings.get(i));
      if (cookie == null) continue;
      if (cookies == null) cookies = new ArrayList<>();
      cookies.add(cookie);
    }

    return cookies != null
        ? Collections.unmodifiableList(cookies)
        : Collections.<Cookie>emptyList();
  }

那麼如果要使用cookie只需要實現CookieJar接口並傳給okhttpclient即可。

接下來可以看下http請求中關於cookie運用的例子

okhttp也就是根據這個規則解析respose中的cookie在下次請求的時候在帶上cookie信息。

1.3 Gzip壓縮

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

    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {//如果沒有Accept-Encoding,則添加默認值,告訴服務器客戶端可以接受gzip壓縮數據
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }

    Response networkResponse = chain.proceed(requestBuilder.build());
    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);

    if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {//response使用的gzip壓縮
      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)));//進行gzip解壓還原數據
    }

    return responseBuilder.build();//返回給我們
  }

okhttp默認在頭部添加Accept-Encoding:gzip,如果服務端確實支持gzip壓縮的話,okhttp會幫我們進行解壓省去了自行解壓的過程。

2. 總結

BridgeInterceptor非常簡單就不總結了,就醬

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章