OKhttp的請求流程及攔截器

看了兩天OKhttp的請求流程及攔截器,以爲有必要寫一下,鞏固一下。html

提問問題:java

一、OKhttp如何發送請求?web

二、如何去處理這些請求的?json

首先咱們來看一下OKhttp的簡單使用?數組

異步請求:緩存

private void studyOkHttp(){
    OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
    Request request = new Request.Builder().url("https://www.baidu.com").build();
    okHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
        }
        @Override
        public void onResponse(Call call, Response response) throws IOException {
            if (response.isSuccessful()){
                KLog.i(response.body().string());
            }
        }
    });
}
複製代碼

代碼我就不解釋了,你們應該都可以看懂。服務器

下面咱們來看一下HttpUrlConnection如何使用,由於你們可能對這個會比較熟悉,結合着這個,想必更加的好理解。cookie

private void studyHttpUrlConnection(){
    try {
        URL url = new URL("https://www.baidu.com");
        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
        //設置鏈接超時,2000ms
        urlConnection.setConnectTimeout(2000);
        //設置指定時間內服務器沒有返回數據的超時,5000ms
        urlConnection.setReadTimeout(5000);
        //設置參數
        urlConnection.setDoOutput(true);   //須要輸出
        urlConnection.setDoInput(true);   //須要輸入
        urlConnection.setUseCaches(false);  //不容許緩存
        urlConnection.setRequestMethod("POST");
        //設置請求屬性,給請求頭添加東西
        urlConnection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
        urlConnection.setRequestProperty("accept", "application/json");
        urlConnection.setRequestProperty("Connection", "Keep-Alive");// 維持長鏈接
        urlConnection.setRequestProperty("Charset", "UTF-8");
        int resultCode = urlConnection.getResponseCode();//獲取響應碼
        if (HttpURLConnection.HTTP_OK == resultCode) {//表示請求成功
        }
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
複製代碼

不太懂的同窗,能夠再看看HTTP方面的一些知識。下面咱們就開始學習OKhttp的請求了。app

從上面代碼能夠看出,請求無非就三步異步

  1. 建立一個Request

  2. 建立一個OKhttpClient

  3. 經過OKhttpClient把Request發送給服務端,而後拿到Response,Response中有咱們須要的一切東西

那麼這個過程就算完成了。整個過程很方便,你所須要的通常都會知足。因此要一探究竟。

固然,Request能夠添加header ,添加body,請求方法等等,這些能夠結合HttpUrlConnection中設置屬性中對比着看,就很好理解。

咱們進入OKhttpClient的newCall方法

看到以下方法

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

返回一個Call,RealCall應該就是Call的實現,接着進入newRealCall方法

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.eventListener = client.eventListenerFactory().create(call);
  return call;
}
複製代碼

緊接着又調用了void enqueue(Callback responseCallback);方法,因爲RealCall實現了Call,因此真正執行的就是RealCall中的enqueue方法

@Override public Response execute() throws IOException {
 //。。。省略了部分代碼
  try {
    client.dispatcher().executed(this);
    Response result = getResponseWithInterceptorChain();
    if (result == null) throw new IOException("Canceled");
    return result;
  } catch (IOException e) {
    e = timeoutExit(e);
    eventListener.callFailed(this, e);
    throw e;
  } finally {
    client.dispatcher().finished(this);
  }
}
複製代碼

Response result = getResponseWithInterceptorChain();

從這一行代碼咱們就能夠拿到Response,感受沒有作什麼東西,就拿到告終果,因此咱們來看一下**getResponseWithInterceptorChain()**方法

從名字上能夠看出 獲取返回結果經過攔截器鏈,走了這麼多,纔到咱們的重點,原來這就是咱們須要研究的東西

進入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);
}
複製代碼

從這裏咱們能夠看到,建立了一個攔截器數組,分別放入了

  1. client.interceptors() : 用戶自定義的攔截器
  2. RetryAndFollowUpInterceptor:負責重定向。
  3. BridgeInterceptor:負責把用戶構造的請求轉換爲發送給服務器的請求,把服務器返回的響應轉換爲對用戶友好的響應。
  4. CacheInterceptor:負責讀取緩存以及更新緩存。
  5. ConnectInterceptor:負責與服務器創建鏈接。
  6. CallServerInterceptor:負責從服務器讀取響應的數據。

最後經過Interceptor.Chain繼續執行process,把Request發送出去,而後獲取到Response,咱們首先要了解他們是怎麼個調用過程,知道了調用過程咱們再具體分析每一個攔截器中都作了什麼操做?(過程是重點)

咱們來看

chain.proceed(originalRequest),也就是RealInterceptorChain中的proceed中的方法

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException {
  if (index >= interceptors.size()) throw new AssertionError();
  calls++;
  // If we already have a stream, confirm that the incoming request will use it.
  if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
    throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
        + " must retain the same host and port");
  }
  // 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");
  }
  // Call the next interceptor in the chain.
  RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,writeTimeout);
  Interceptor interceptor = interceptors.get(index);
  Response response = interceptor.intercept(next);
  // Confirm that the next interceptor made its required call to chain.proceed().
  if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
    throw new IllegalStateException("network interceptor " + interceptor
        + " must call proceed() exactly once");
  }
  // Confirm that the intercepted response isn't null.
  if (response == null) {
    throw new NullPointerException("interceptor " + interceptor + " returned null");
  }
  if (response.body() == null) {
    throw new IllegalStateException(
        "interceptor " + interceptor + " returned a response with no body");
  }
  return response;
}
複製代碼

代碼很少,我就都貼出來了,咱們主要看這幾行代碼

// Call the next interceptor in the chain.
  RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
      connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
      writeTimeout);
  Interceptor interceptor = interceptors.get(index);
  Response response = interceptor.intercept(next);
複製代碼
這是一個很奇妙的設計

執行proceed時,index傳進來的是0,因此interceptors獲取到第一個攔截器,通常是咱們本身定義的,若是沒有定義,那就回獲取到RetryAndFollowUpInterceptor攔截器,而後就會調用RetryAndFollowUpInterceptor的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 streamAllocation = new StreamAllocation(client.connectionPool(),
      createAddress(request.url()), call, eventListener, callStackTrace);
  this.streamAllocation = streamAllocation;
  Response priorResponse = null;
  while (true) {
    if (canceled) {
      streamAllocation.release();
      throw new IOException("Canceled");
    }

    Response response;
    boolean releaseConnection = true;
    try {
      response = realChain.proceed(request, streamAllocation, null, null);
      releaseConnection = false;
    } catch (RouteException e) {

}
//省略了多行代碼
      
}
複製代碼

主要看這行代碼response = realChain.proceed(request, streamAllocation,null,null);

realChain目前是RealInterceptorChain,而後又從新調用了proceed方法,也就是說用從新調用了RealInterceptorChain中的proceed中的方法,因此又回來了,

因爲在第一次調用的時候,index傳進來是0,在這裏,又從新建立了一個RealInterceptorChain ,因此在RetryAndFollowUpInterceptor獲取的到RealInterceptorChain 已經變成了1,而後緊接着獲取interceptors中第二個攔截器,也就是BridgeInterceptor,緊接着又執行了BridgeInterceptor中的intercept方法,咱們去BridgeInterceptor中看這個方法

@Override public Response intercept(Chain chain) throws IOException {
  Request userRequest = chain.request();
  Request.Builder requestBuilder = userRequest.newBuilder();
  Response networkResponse = chain.proceed(requestBuilder.build());
  return responseBuilder.build();
}
複製代碼

很熟悉的影子chain.proceed(requestBuilder.build());又看到了這個方法,也就是說用從新調用了RealInterceptorChain中的proceed中的方法,因此又回到了RealInterceptorChain 中的proceed中,這時候index已經變成了2,就能夠獲得CacheInterceptor攔截器,以此類推,會分別輪詢一遍攔截器

RetryAndFollowUpInterceptor -> BridgeInterceptor -> CacheInterceptor -> ConnectInterceptor -> CallServerInterceptor。

要想拿到RetryAndFollowUpInterceptor的Response,就須要拿到BridgeInterceptor 的Response,要想拿到BridgeInterceptor的Response,就須要拿到CacheInterceptor 的Response,因此最終先拿到CallServerInterceptor的Response返回給ConnectInterceptor,依次類推,就是這樣的一個過程。

各個攔截器中都作了什麼操做呢?能夠參考以下兩篇文章

文章一

文章二

因此咱們來分析一下日誌攔截器HttpLoggingInterceptor,咱們看一下intercept方法

@Override public Response intercept(Chain chain) throws IOException {
  Level level = this.level;

//獲取一個Request 
  Request request = chain.request();
//若是設置了NONE,就不會打印,直接去調用下一個攔截器
  if (level == Level.NONE) {
    return chain.proceed(request);
  }

  boolean logBody = level == Level.BODY;
  boolean logHeaders = logBody || level == Level.HEADERS;

//獲取請求體
  RequestBody requestBody = request.body();
  boolean hasRequestBody = requestBody != null;

//獲取Connection
  Connection connection = chain.connection();
  String requestStartMessage = "--> "
      + request.method()
      + ' ' + request.url()
      + (connection != null ? " " + connection.protocol() : "");
  if (!logHeaders && hasRequestBody) {
    requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
		//打印請求方法 請求rul 請求端口 請求的字節長度
  }
  logger.log(requestStartMessage);

  if (logHeaders) {
    if (hasRequestBody) {
      // Request body headers are only present when installed as a network interceptor. Force
      // them to be included (when available) so there values are known.
      if (requestBody.contentType() != null) {
        logger.log("Content-Type: " + requestBody.contentType());
      }
      if (requestBody.contentLength() != -1) {
        logger.log("Content-Length: " + requestBody.contentLength());
      }
    }
//獲取Header
    Headers headers = request.headers();
    for (int i = 0, count = headers.size(); i < count; i++) {
      String name = headers.name(i);
      // Skip headers from the request body as they are explicitly logged above.
      if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
        logger.log(name + ": " + headers.value(i));
//獲取header的名字和值
      }
    }

    if (!logBody || !hasRequestBody) {
      logger.log("--> END " + request.method());
    } else if (bodyHasUnknownEncoding(request.headers())) {
      logger.log("--> END " + request.method() + " (encoded body omitted)");
    } else {
      Buffer buffer = new Buffer();
      requestBody.writeTo(buffer);

      Charset charset = UTF8;
      MediaType contentType = requestBody.contentType();
      if (contentType != null) {
        charset = contentType.charset(UTF8);
      }

      logger.log("");
      if (isPlaintext(buffer)) {
        logger.log(buffer.readString(charset));
        logger.log("--> END " + request.method()
            + " (" + requestBody.contentLength() + "-byte body)");
      } else {
        logger.log("--> END " + request.method() + " (binary "
            + requestBody.contentLength() + "-byte body omitted)");
      }
    }
  }

  long startNs = System.nanoTime();
  Response response;
  try {
    response = chain.proceed(request);
//這個地方要注意一下,每一個攔截器中都會調用這個方法,若是沒有這個方法,那就不會繼續訪問其餘的攔截器了
  } catch (Exception e) {
    logger.log("<-- HTTP FAILED: " + e);
    throw e;
  }
  long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);

  ResponseBody responseBody = response.body();
  long contentLength = responseBody.contentLength();
  String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
  logger.log("<-- "
      + response.code()
      + (response.message().isEmpty() ? "" : ' ' + response.message())
      + ' ' + response.request().url()
      + " (" + tookMs + "ms" + (!logHeaders ? ", " + bodySize + " body" : "") + ')');

  if (logHeaders) {
    Headers headers = response.headers();
    for (int i = 0, count = headers.size(); i < count; i++) {
      logger.log(headers.name(i) + ": " + headers.value(i));
    }

    if (!logBody || !HttpHeaders.hasBody(response)) {
      logger.log("<-- END HTTP");
    } else if (bodyHasUnknownEncoding(response.headers())) {
      logger.log("<-- END HTTP (encoded body omitted)");
    } else {
      BufferedSource source = responseBody.source();
      source.request(Long.MAX_VALUE); // Buffer the entire body.
      Buffer buffer = source.buffer();

      Long gzippedLength = null;
      if ("gzip".equalsIgnoreCase(headers.get("Content-Encoding"))) {
        gzippedLength = buffer.size();
        GzipSource gzippedResponseBody = null;
        try {
          gzippedResponseBody = new GzipSource(buffer.clone());
          buffer = new Buffer();
          buffer.writeAll(gzippedResponseBody);
        } finally {
          if (gzippedResponseBody != null) {
            gzippedResponseBody.close();
          }
        }
      }

      Charset charset = UTF8;
      MediaType contentType = responseBody.contentType();
      if (contentType != null) {
        charset = contentType.charset(UTF8);
      }

      if (!isPlaintext(buffer)) {
        logger.log("");
        logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");
        return response;
      }

      if (contentLength != 0) {
        logger.log("");
        logger.log(buffer.clone().readString(charset));
      }

      if (gzippedLength != null) {
          logger.log("<-- END HTTP (" + buffer.size() + "-byte, "
              + gzippedLength + "-gzipped-byte body)");
      } else {
          logger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
      }
    }
  }

  return response;
}
複製代碼

這樣就會把請求的body以及返回的Response中的信息打印出來了,就是這樣的一個流程。不知道你們明白沒有

個人公衆號
相關文章
相關標籤/搜索