安卓面試清單----OKHttp源碼解析(三)

來源:互聯網
上載者:User
sendRequest ()

上篇文章我們講了sendRequest ()方法,這節接著來看readResponse方法:

/**來自HttpEngine 類*/ public void readResponse() throws IOException {      if(this.userResponse == null) {         if(this.networkRequest == null && this.cacheResponse == null) {            throw new IllegalStateException("call sendRequest() first!");         } else if(this.networkRequest != null) {            Response networkResponse;            if(this.forWebSocket) {               this.httpStream.writeRequestHeaders(this.networkRequest);               networkResponse = this.readNetworkResponse();            } else if(!this.callerWritesRequestBody) {             // 先執行攔截器,再寫入request到HttpStream的Sinkbuffer中,最後發送buffer,並讀取response               networkResponse = (new HttpEngine.NetworkInterceptorChain(0, this.networkRequest)).proceed(this.networkRequest);            } else {               if(this.bufferedRequestBody != null && this.bufferedRequestBody.buffer().size() > 0L) {               // 將request body的buffer發出去,這樣requestBodyOut中就有了body                  this.bufferedRequestBody.emit();               }               if(this.sentRequestMillis == -1L) {                  if(OkHeaders.contentLength(this.networkRequest) == -1L && this.requestBodyOut instanceof RetryableSink) {                     long responseCache = ((RetryableSink)this.requestBodyOut).contentLength();                     this.networkRequest = this.networkRequest.newBuilder().header("Content-Length", Long.toString(responseCache)).build();                  }                  this.httpStream.writeRequestHeaders(this.networkRequest);               }               if(this.requestBodyOut != null) {                  if(this.bufferedRequestBody != null) {                     this.bufferedRequestBody.close();                  } else {                     this.requestBodyOut.close();                  }                  if(this.requestBodyOut instanceof RetryableSink) {                // body 寫入socket中                                   this.httpStream.writeRequestBody((RetryableSink)this.requestBodyOut);                  }               }               networkResponse = this.readNetworkResponse();            }            this.receiveHeaders(networkResponse.headers());            if(this.cacheResponse != null) {               if(validate(this.cacheResponse, networkResponse)) {                  this.userResponse = this.cacheResponse.newBuilder().request(this.userRequest).priorResponse(stripBody(this.priorResponse)).headers(combine(this.cacheResponse.headers(), networkResponse.headers())).cacheResponse(stripBody(this.cacheResponse)).networkResponse(stripBody(networkResponse)).build();                  networkResponse.body().close();                  this.releaseStreamAllocation();                  InternalCache responseCache1 = Internal.instance.internalCache(this.client);                  responseCache1.trackConditionalCacheHit();                  responseCache1.update(this.cacheResponse, stripBody(this.userResponse));                  this.userResponse = this.unzip(this.userResponse);                  return;               }               Util.closeQuietly(this.cacheResponse.body());            }            this.userResponse = networkResponse.newBuilder().request(this.userRequest).priorResponse(stripBody(this.priorResponse)).cacheResponse(stripBody(this.cacheResponse)).networkResponse(stripBody(networkResponse)).build();            if(hasBody(this.userResponse)) {               this.maybeCache();               this.userResponse = this.unzip(this.cacheWritingResponse(this.storeRequest, this.userResponse));            }         }      }   }

定位到第8行:

this.httpStream.writeRequestHeaders(this.networkRequest);

writeRequestHeaders這個方法是HttpStream 介面的方法,由Http1xStream和Http2xStream重寫。

如果採用http1.x協議,則執行Http1xStream裡面的writeRequestHeaders方法,如果為http2.0,則執行Http2xStream的。由上文我們知道這取決於請求是http還是https。我們以1x為類:

/**來自 Http1xStream 類*/public void writeRequestHeaders(Request request) throws IOException {        this.httpEngine.writingRequestHeaders();        String requestLine = RequestLine.get(request, this.httpEngine                .getConnection().route().proxy().type());        this.writeRequest(request.headers(), requestLine);    }

首先得到請求的RequestLine(StatusLine),這個值由方法名、URL,Http協議拼接而成。
其次執行writeRequest方法:

/**來自 Http1xStream 類*/public void writeRequest(Headers headers, String requestLine)            throws IOException {        if (this.state != 0) {            throw new IllegalStateException("state: " + this.state);        } else {            this.sink.writeUtf8(requestLine).writeUtf8("\r\n");            int i = 0;            for (int size = headers.size(); i < size; ++i) {                this.sink.writeUtf8(headers.name(i)).writeUtf8(": ")                        .writeUtf8(headers.value(i)).writeUtf8("\r\n");            }            this.sink.writeUtf8("\r\n");            this.state = 1;        }    }

主要是將StatusLine和header資訊寫入sink,sink是什麼呢,因為從上篇我們知道OKhttp底部是socket通訊,所以sink就相當於我們在httpUrlConnection中使用的inputStream,它是socket的寫入流,而source就是OutPutStream。 readNetworkResponse()

再看第9行:

/**來自 HttpEngine 類*/private Response readNetworkResponse() throws IOException {     //對sink流執行flush操作      this.httpStream.finishRequest();      //等待伺服器相應並讀取伺服器返回資訊組裝成我們需要的response      Response networkResponse = this.httpStream.readResponseHeaders().request(this.networkRequest).handshake(this.streamAllocation.connection().handshake()).header(OkHeaders.SENT_MILLIS, Long.toString(this.sentRequestMillis)).header(OkHeaders.RECEIVED_MILLIS, Long.toString(System.currentTimeMillis())).build();      if(!this.forWebSocket) {         networkResponse = networkResponse.newBuilder().body(this.httpStream.openResponseBody(networkResponse)).build();      }      if("close".equalsIgnoreCase(networkResponse.request().header("Connection")) || "close".equalsIgnoreCase(networkResponse.header("Connection"))) {         this.streamAllocation.noNewStreams();      }      return networkResponse;   }

看下這個方法第7行是怎麼組裝的呢。

/**來自Http1xStream 類(下面是兩個方法)*/public Builder readResponseHeaders() throws IOException {        return this.readResponse();    }    public Builder readResponse() throws IOException {        if (this.state != 1 && this.state != 3) {            throw new IllegalStateException("state: " + this.state);        } else {            try {                StatusLine e;                Builder exception1;                 // 如果返回code不是100, 則直接將Response對象返回        // 對於100,continue,server還會繼續返回response string,我們需要在while迴圈中繼續接收並解析                do {                    e = StatusLine.parse(this.source.readUtf8LineStrict());                     // 從輸入資料流裡讀出回覆並組裝成回覆訊息,放入構造的Response的工廠類Build中                    exception1 = (new Builder()).protocol(e.protocol)                            .code(e.code).message(e.message)                            //回覆頭部的讀取                            .headers(this.readHeaders());                } while (e.code == 100);                this.state = 4;                return exception1;            } catch (EOFException arg2) {                IOException exception = new IOException(                        "unexpected end of stream on " + this.streamAllocation);                exception.initCause(arg2);                throw exception;            }        }    }

上面主要是對回覆頭部的資訊進行整理,而readNetworkResponse方法的第13行主要是對服務返回的body進行組裝整理:

/**來自Http1xStream 類(下面是兩個方法)*/public ResponseBody openResponseBody(Response response) throws IOException {        Source source = this.getTransferStream(response);        return new RealResponseBody(response.headers(), Okio.buffer(source));    }    private Source getTransferStream(Response response) throws IOException {        if (!HttpEngine.hasBody(response)) {            return this.newFixedLengthSource(0L);        } else if ("chunked".equalsIgnoreCase(response                .header("Transfer-Encoding"))) {            return this.newChunkedSource(this.httpEngine);        } else {            long contentLength = OkHeaders.contentLength(response);            return contentLength != -1L ? this                    .newFixedLengthSource(contentLength) : this                    .newUnknownLengthSource();        }    }
this.newChunkedSource(this.httpEngine);

這句代碼最後執行到ChunkedSource (實現source介面)的 read方法:

    public long read(Buffer sink, long byteCount) throws IOException {            if (byteCount < 0L) {                throw new IllegalArgumentException("byteCount < 0: "                        + byteCount);            } else if (this.closed) {                throw new IllegalStateException("closed");            } else if (!this.hasMoreChunks) {                return -1L;            } else {                if (this.bytesRemainingInChunk == 0L                        || this.bytesRemainingInChunk == -1L) {                    this.readChunkSize();                    if (!this.hasMoreChunks) {                        return -1L;                    }                }                long read = Http1xStream.this.source.read(sink,                        Math.min(byteCount, this.bytesRemainingInChunk));                if (read == -1L) {                    this.endOfInput(false);                    throw new ProtocolException("unexpected end of stream");                } else {                    this.bytesRemainingInChunk -= read;                    return read;                }            }        }

上面我們已經分析了Source是我們從服務讀取的輸入資料流,類似於OutPutStream,read方法則是從服務讀取。

最終,回到readResponse方法的第9行,我們得到了完整的networkResponse。

我們再來看看validate(cacheResponse, networkResponse)方法是如何判斷緩衝是否可用的:

/**來自HttpEngine 類的ReadResponse方法*/  private static boolean validate(Response cached, Response network) {    //如果伺服器返回304則緩衝有效      if(network.code() == 304) {         return true;      } else {         Date lastModified = cached.headers().getDate("Last-Modified");            //通過緩衝和網路請求響應中的Last-Modified來計算是否是最新資料,如果是則緩衝有效         if(lastModified != null) {            Date networkLastModified = network.headers().getDate("Last-Modified");            if(networkLastModified != null && networkLastModified.getTime() < lastModified.getTime()) {               return true;            }         }         return false;      }   }

cache response存在的情況下,應該是緩衝到期或者強制放棄緩衝,在此情況下,緩衝策略全部交給伺服器判斷,用戶端只用發送條件get請求來驗證cache的內容是否有變更即可,如果緩衝是有效,則返回304 Not Modifiled,且response中不會包含body,否則cache改變,回複200, OK。response中包含body。條件get請求有兩種方式一種是Last-Modified-Date,一種是 ETag。這裡採用了Last-Modified-Date,通過緩衝和網路請求響應中的Last-Modified來計算是否是最新資料,如果是則緩衝有效。

回到第一篇文章尋找兩個個參數 forWebSocket 和 callerWritesRequestBody,可以發現,這兩個參數都為false,那麼就是說
在readResponse方法中預設是不會執行第8、9行的,而是會去執行第11行,我們分析過發送請求時使用的攔截器模式,這裡對回覆的操作也用了同樣的方式,不同於請求調用的是intercept,這裡用的是proceed。所以我們有必要再分析以下這個攔截器,
重點是 proceed方法:

/**來自HttpEngine 的內部類 NetworkInterceptorChain 實現了Chain介面*/  public Response proceed(Request request) throws IOException {         ++this.calls;         if(this.index > 0) {            Interceptor response = (Interceptor)HttpEngine.this.client.networkInterceptors().get(this.index - 1);            Address code = this.connection().route().address();            if(!request.url().host().equals(code.url().host()) || request.url().port() != code.url().port()) {               throw new IllegalStateException("network interceptor " + response + " must retain the same host and port");            }            if(this.calls > 1) {               throw new IllegalStateException("network interceptor " + response + " must call proceed() exactly once");            }         }         if(this.index < HttpEngine.this.client.networkInterceptors().size()) {         //根據攔截器的數目取出攔截器並執行intercept裡面使用者自訂的處理方式,和我們之前分析過的一樣         //            HttpEngine.NetworkInterceptorChain arg6 = HttpEngine.this.new NetworkInterceptorChain(this.index + 1, request);            Interceptor arg9 = (Interceptor)HttpEngine.this.client.networkInterceptors().get(this.index);            Response interceptedResponse = arg9.intercept(arg6);            if(arg6.calls != 1) {               throw new IllegalStateException("network interceptor " + arg9 + " must call proceed() exactly once");            } else if(interceptedResponse == null) {               throw new NullPointerException("network interceptor " + arg9 + " returned null");            } else {               return interceptedResponse;            }         } else {             //寫入要求標頭部             HttpEngine.this.httpStream.writeRequestHeaders(request);            HttpEngine.this.networkRequest = request;             //寫入一些請求體               Sink arg4 = HttpEngine.this.httpStream.createRequestBody(request, request.body().contentLength());               BufferedSink arg7 = Okio.buffer(arg4);               request.body().writeTo(arg7);               arg7.close();            }            //將之前寫入的資料flush給socket並讀取伺服器回覆            Response arg5 = HttpEngine.this.readNetworkResponse();            int arg8 = arg5.code();            if((arg8 == 204 || arg8 == 205) && arg5.body().contentLength() > 0L) {               throw new ProtocolException("HTTP " + arg8 + " had non-zero Content-Length: " + arg5.body().contentLength());            } else {               return arg5;            }         }      }

裡面的writeRequestHeader方法和 readNetworkResponse 方法我們已經分析過了。再經過這麼多7788的跳轉、嵌套,終於拿到了我們需要的Response。最後回到我們第一篇文章最初的getResponse 方法,找到這兩句:

            Response arg22 = this.engine.getResponse();            Request arg23 = this.engine.followUpRequest();

第一句很明顯是得到我們的response,直接返回userResponse。
第二句是對請求結果發生重新導向時的處理,client發送一個request之後,server可能回複一個重新導向的response,並在這個response中告知client需要重新訪問的server的IP。此時,client需要重新向新的server發送request,並等待新server的回複。所以我們需要單獨判斷重新導向response,並發送多次request。有了OKHttp,這一切你都不用管,它會自動幫你完成所有這一切。OKHttp中followUpRequest()方法就是完成這個功能的。 總結

OKHttp底層源碼還是相當複雜的,畢竟它的功能如此之強大。OKHttp預設採用了Keep-Alive持久串連技術(並不代表一定長串連,取決於伺服器),可支援gzip編碼的response。在cache的處理上,如果cache可用,則直接使用cache,否則使用網路資料。OKHttp會做cache到期的判斷和到期後的再驗證。有了OKHttp,這一切你都不用管,它幫你cover掉了。

當需要做使用者驗證和重新導向時,我們一般需要發送認證request,或向新server發送request,也就是要重新再產生新request並發送出去。有了OKHttp,這一切你都不用管,它又幫你cover掉了。 後期

1、研讀幾遍更新其中的錯誤和不足的點。
2、完善流程圖,做到全面理解。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.