• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  */
17 
18 package com.squareup.okhttp.internal.http;
19 
20 import com.squareup.okhttp.Address;
21 import com.squareup.okhttp.CertificatePinner;
22 import com.squareup.okhttp.Connection;
23 import com.squareup.okhttp.ConnectionPool;
24 import com.squareup.okhttp.Headers;
25 import com.squareup.okhttp.HttpUrl;
26 import com.squareup.okhttp.Interceptor;
27 import com.squareup.okhttp.MediaType;
28 import com.squareup.okhttp.OkHttpClient;
29 import com.squareup.okhttp.Protocol;
30 import com.squareup.okhttp.Request;
31 import com.squareup.okhttp.Response;
32 import com.squareup.okhttp.ResponseBody;
33 import com.squareup.okhttp.Route;
34 import com.squareup.okhttp.internal.Internal;
35 import com.squareup.okhttp.internal.InternalCache;
36 import com.squareup.okhttp.internal.Util;
37 import com.squareup.okhttp.internal.Version;
38 import java.io.IOException;
39 import java.io.InterruptedIOException;
40 import java.net.CookieHandler;
41 import java.net.ProtocolException;
42 import java.net.Proxy;
43 import java.net.SocketTimeoutException;
44 import java.security.cert.CertificateException;
45 import java.util.Date;
46 import java.util.List;
47 import java.util.Map;
48 import javax.net.ssl.HostnameVerifier;
49 import javax.net.ssl.SSLHandshakeException;
50 import javax.net.ssl.SSLPeerUnverifiedException;
51 import javax.net.ssl.SSLSocketFactory;
52 import okio.Buffer;
53 import okio.BufferedSink;
54 import okio.BufferedSource;
55 import okio.GzipSource;
56 import okio.Okio;
57 import okio.Sink;
58 import okio.Source;
59 import okio.Timeout;
60 
61 import static com.squareup.okhttp.internal.Util.closeQuietly;
62 import static com.squareup.okhttp.internal.http.StatusLine.HTTP_CONTINUE;
63 import static com.squareup.okhttp.internal.http.StatusLine.HTTP_PERM_REDIRECT;
64 import static com.squareup.okhttp.internal.http.StatusLine.HTTP_TEMP_REDIRECT;
65 import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
66 import static java.net.HttpURLConnection.HTTP_MOVED_TEMP;
67 import static java.net.HttpURLConnection.HTTP_MULT_CHOICE;
68 import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED;
69 import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
70 import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
71 import static java.net.HttpURLConnection.HTTP_SEE_OTHER;
72 import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
73 import static java.util.concurrent.TimeUnit.MILLISECONDS;
74 
75 /**
76  * Handles a single HTTP request/response pair. Each HTTP engine follows this
77  * lifecycle:
78  * <ol>
79  * <li>It is created.
80  * <li>The HTTP request message is sent with sendRequest(). Once the request
81  * is sent it is an error to modify the request headers. After
82  * sendRequest() has been called the request body can be written to if
83  * it exists.
84  * <li>The HTTP response message is read with readResponse(). After the
85  * response has been read the response headers and body can be read.
86  * All responses have a response body input stream, though in some
87  * instances this stream is empty.
88  * </ol>
89  *
90  * <p>The request and response may be served by the HTTP response cache, by the
91  * network, or by both in the event of a conditional GET.
92  */
93 public final class HttpEngine {
94   /**
95    * How many redirects and auth challenges should we attempt? Chrome follows 21 redirects; Firefox,
96    * curl, and wget follow 20; Safari follows 16; and HTTP/1.0 recommends 5.
97    */
98   public static final int MAX_FOLLOW_UPS = 20;
99 
100   private static final ResponseBody EMPTY_BODY = new ResponseBody() {
101     @Override public MediaType contentType() {
102       return null;
103     }
104     @Override public long contentLength() {
105       return 0;
106     }
107     @Override public BufferedSource source() {
108       return new Buffer();
109     }
110   };
111 
112   final OkHttpClient client;
113 
114   private Connection connection;
115   private Address address;
116   private RouteSelector routeSelector;
117   private Route route;
118   private final Response priorResponse;
119 
120   private Transport transport;
121 
122   /** The time when the request headers were written, or -1 if they haven't been written yet. */
123   long sentRequestMillis = -1;
124 
125   /**
126    * True if this client added an "Accept-Encoding: gzip" header field and is
127    * therefore responsible for also decompressing the transfer stream.
128    */
129   private boolean transparentGzip;
130 
131   /**
132    * True if the request body must be completely buffered before transmission;
133    * false if it can be streamed. Buffering has two advantages: we don't need
134    * the content-length in advance and we can retransmit if necessary. The
135    * upside of streaming is that we can save memory.
136    */
137   public final boolean bufferRequestBody;
138 
139   /**
140    * The original application-provided request. Never modified by OkHttp. When
141    * follow-up requests are necessary, they are derived from this request.
142    */
143   private final Request userRequest;
144 
145   /**
146    * The request to send on the network, or null for no network request. This is
147    * derived from the user request, and customized to support OkHttp features
148    * like compression and caching.
149    */
150   private Request networkRequest;
151 
152   /**
153    * The cached response, or null if the cache doesn't exist or cannot be used
154    * for this request. Conditional caching means this may be non-null even when
155    * the network request is non-null. Never modified by OkHttp.
156    */
157   private Response cacheResponse;
158 
159   /**
160    * The user-visible response. This is derived from either the network
161    * response, cache response, or both. It is customized to support OkHttp
162    * features like compression and caching.
163    */
164   private Response userResponse;
165 
166   private Sink requestBodyOut;
167   private BufferedSink bufferedRequestBody;
168   private final boolean callerWritesRequestBody;
169   private final boolean forWebSocket;
170 
171   /** The cache request currently being populated from a network response. */
172   private CacheRequest storeRequest;
173   private CacheStrategy cacheStrategy;
174 
175   /**
176    * @param request the HTTP request without a body. The body must be written via the engine's
177    *     request body stream.
178    * @param callerWritesRequestBody true for the {@code HttpURLConnection}-style interaction
179    *     model where control flow is returned to the calling application to write the request body
180    *     before the response body is readable.
181    * @param connection the connection used for an intermediate response immediately prior to this
182    *     request/response pair, such as a same-host redirect. This engine assumes ownership of the
183    *     connection and must release it when it is unneeded.
184    * @param routeSelector the route selector used for a failed attempt immediately preceding this
185    */
HttpEngine(OkHttpClient client, Request request, boolean bufferRequestBody, boolean callerWritesRequestBody, boolean forWebSocket, Connection connection, RouteSelector routeSelector, RetryableSink requestBodyOut, Response priorResponse)186   public HttpEngine(OkHttpClient client, Request request, boolean bufferRequestBody,
187       boolean callerWritesRequestBody, boolean forWebSocket, Connection connection,
188       RouteSelector routeSelector, RetryableSink requestBodyOut, Response priorResponse) {
189     this.client = client;
190     this.userRequest = request;
191     this.bufferRequestBody = bufferRequestBody;
192     this.callerWritesRequestBody = callerWritesRequestBody;
193     this.forWebSocket = forWebSocket;
194     this.connection = connection;
195     this.routeSelector = routeSelector;
196     this.requestBodyOut = requestBodyOut;
197     this.priorResponse = priorResponse;
198 
199     if (connection != null) {
200       Internal.instance.setOwner(connection, this);
201       this.route = connection.getRoute();
202     } else {
203       this.route = null;
204     }
205   }
206 
207   /**
208    * Figures out what the response source will be, and opens a socket to that
209    * source if necessary. Prepares the request headers and gets ready to start
210    * writing the request body if it exists.
211    *
212    * @throws RequestException if there was a problem with request setup. Unrecoverable.
213    * @throws RouteException if the was a problem during connection via a specific route. Sometimes
214    *     recoverable. See {@link #recover(RouteException)}.
215    * @throws IOException if there was a problem while making a request. Sometimes recoverable. See
216    *     {@link #recover(IOException)}.
217    *
218    */
sendRequest()219   public void sendRequest() throws RequestException, RouteException, IOException {
220     if (cacheStrategy != null) return; // Already sent.
221     if (transport != null) throw new IllegalStateException();
222 
223     Request request = networkRequest(userRequest);
224 
225     InternalCache responseCache = Internal.instance.internalCache(client);
226     Response cacheCandidate = responseCache != null
227         ? responseCache.get(request)
228         : null;
229 
230     long now = System.currentTimeMillis();
231     cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
232     networkRequest = cacheStrategy.networkRequest;
233     cacheResponse = cacheStrategy.cacheResponse;
234 
235     if (responseCache != null) {
236       responseCache.trackResponse(cacheStrategy);
237     }
238 
239     if (cacheCandidate != null && cacheResponse == null) {
240       closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
241     }
242 
243     if (networkRequest != null) {
244       // Open a connection unless we inherited one from a redirect.
245       if (connection == null) {
246         connect();
247       }
248 
249       transport = Internal.instance.newTransport(connection, this);
250 
251       // If the caller's control flow writes the request body, we need to create that stream
252       // immediately. And that means we need to immediately write the request headers, so we can
253       // start streaming the request body. (We may already have a request body if we're retrying a
254       // failed POST.)
255       if (callerWritesRequestBody && permitsRequestBody() && requestBodyOut == null) {
256         long contentLength = OkHeaders.contentLength(request);
257         if (bufferRequestBody) {
258           if (contentLength > Integer.MAX_VALUE) {
259             throw new IllegalStateException("Use setFixedLengthStreamingMode() or "
260                 + "setChunkedStreamingMode() for requests larger than 2 GiB.");
261           }
262 
263           if (contentLength != -1) {
264             // Buffer a request body of a known length.
265             transport.writeRequestHeaders(networkRequest);
266             requestBodyOut = new RetryableSink((int) contentLength);
267           } else {
268             // Buffer a request body of an unknown length. Don't write request
269             // headers until the entire body is ready; otherwise we can't set the
270             // Content-Length header correctly.
271             requestBodyOut = new RetryableSink();
272           }
273         } else {
274           transport.writeRequestHeaders(networkRequest);
275           requestBodyOut = transport.createRequestBody(networkRequest, contentLength);
276         }
277       }
278 
279     } else {
280       // We aren't using the network. Recycle a connection we may have inherited from a redirect.
281       if (connection != null) {
282         Internal.instance.recycle(client.getConnectionPool(), connection);
283         connection = null;
284       }
285 
286       if (cacheResponse != null) {
287         // We have a valid cached response. Promote it to the user response immediately.
288         this.userResponse = cacheResponse.newBuilder()
289             .request(userRequest)
290             .priorResponse(stripBody(priorResponse))
291             .cacheResponse(stripBody(cacheResponse))
292             .build();
293       } else {
294         // We're forbidden from using the network, and the cache is insufficient.
295         this.userResponse = new Response.Builder()
296             .request(userRequest)
297             .priorResponse(stripBody(priorResponse))
298             .protocol(Protocol.HTTP_1_1)
299             .code(504)
300             .message("Unsatisfiable Request (only-if-cached)")
301             .body(EMPTY_BODY)
302             .build();
303       }
304 
305       userResponse = unzip(userResponse);
306     }
307   }
308 
stripBody(Response response)309   private static Response stripBody(Response response) {
310     return response != null && response.body() != null
311         ? response.newBuilder().body(null).build()
312         : response;
313   }
314 
315   /** Connect to the origin server either directly or via a proxy. */
connect()316   private void connect() throws RequestException, RouteException {
317     if (connection != null) throw new IllegalStateException();
318 
319     if (routeSelector == null) {
320       address = createAddress(client, networkRequest);
321       try {
322         routeSelector = RouteSelector.get(address, networkRequest, client);
323       } catch (IOException e) {
324         throw new RequestException(e);
325       }
326     }
327 
328     connection = createNextConnection();
329     Internal.instance.connectAndSetOwner(client, connection, this, networkRequest);
330     route = connection.getRoute();
331   }
332 
createNextConnection()333   private Connection createNextConnection() throws RouteException {
334     ConnectionPool pool = client.getConnectionPool();
335 
336     // Always prefer pooled connections over new connections.
337     for (Connection pooled; (pooled = pool.get(address)) != null; ) {
338       if (networkRequest.method().equals("GET") || Internal.instance.isReadable(pooled)) {
339         return pooled;
340       }
341       closeQuietly(pooled.getSocket());
342     }
343 
344     try {
345       Route route = routeSelector.next();
346       return new Connection(pool, route);
347     } catch (IOException e) {
348       throw new RouteException(e);
349     }
350   }
351 
352   /**
353    * Called immediately before the transport transmits HTTP request headers.
354    * This is used to observe the sent time should the request be cached.
355    */
writingRequestHeaders()356   public void writingRequestHeaders() {
357     if (sentRequestMillis != -1) throw new IllegalStateException();
358     sentRequestMillis = System.currentTimeMillis();
359   }
360 
permitsRequestBody()361   boolean permitsRequestBody() {
362     return HttpMethod.permitsRequestBody(userRequest.method());
363   }
364 
365   /** Returns the request body or null if this request doesn't have a body. */
getRequestBody()366   public Sink getRequestBody() {
367     if (cacheStrategy == null) throw new IllegalStateException();
368     return requestBodyOut;
369   }
370 
getBufferedRequestBody()371   public BufferedSink getBufferedRequestBody() {
372     BufferedSink result = bufferedRequestBody;
373     if (result != null) return result;
374     Sink requestBody = getRequestBody();
375     return requestBody != null
376         ? (bufferedRequestBody = Okio.buffer(requestBody))
377         : null;
378   }
379 
hasResponse()380   public boolean hasResponse() {
381     return userResponse != null;
382   }
383 
getRequest()384   public Request getRequest() {
385     return userRequest;
386   }
387 
388   /** Returns the engine's response. */
389   // TODO: the returned body will always be null.
getResponse()390   public Response getResponse() {
391     if (userResponse == null) throw new IllegalStateException();
392     return userResponse;
393   }
394 
getConnection()395   public Connection getConnection() {
396     return connection;
397   }
398 
399   /**
400    * Attempt to recover from failure to connect via a route. Returns a new HTTP engine
401    * that should be used for the retry if there are other routes to try, or null if
402    * there are no more routes to try.
403    */
recover(RouteException e)404   public HttpEngine recover(RouteException e) {
405     if (routeSelector != null && connection != null) {
406       connectFailed(routeSelector, e.getLastConnectException());
407     }
408 
409     if (routeSelector == null && connection == null // No connection.
410         || routeSelector != null && !routeSelector.hasNext() // No more routes to attempt.
411         || !isRecoverable(e)) {
412       return null;
413     }
414 
415     Connection connection = close();
416 
417     // For failure recovery, use the same route selector with a new connection.
418     return new HttpEngine(client, userRequest, bufferRequestBody, callerWritesRequestBody,
419         forWebSocket, connection, routeSelector, (RetryableSink) requestBodyOut, priorResponse);
420   }
421 
isRecoverable(RouteException e)422   private boolean isRecoverable(RouteException e) {
423     // If the application has opted-out of recovery, don't recover.
424     if (!client.getRetryOnConnectionFailure()) {
425       return false;
426     }
427 
428     // Problems with a route may mean the connection can be retried with a new route, or may
429     // indicate a client-side or server-side issue that should not be retried. To tell, we must look
430     // at the cause.
431 
432     IOException ioe = e.getLastConnectException();
433 
434     // If there was a protocol problem, don't recover.
435     if (ioe instanceof ProtocolException) {
436       return false;
437     }
438 
439     // If there was an interruption don't recover, but if there was a timeout
440     // we should try the next route (if there is one).
441     if (ioe instanceof InterruptedIOException) {
442       return ioe instanceof SocketTimeoutException;
443     }
444 
445     // Look for known client-side or negotiation errors that are unlikely to be fixed by trying
446     // again with a different route.
447     if (ioe instanceof SSLHandshakeException) {
448       // If the problem was a CertificateException from the X509TrustManager,
449       // do not retry.
450       if (ioe.getCause() instanceof CertificateException) {
451         return false;
452       }
453     }
454     if (ioe instanceof SSLPeerUnverifiedException) {
455       // e.g. a certificate pinning error.
456       return false;
457     }
458 
459     // An example of one we might want to retry with a different route is a problem connecting to a
460     // proxy and would manifest as a standard IOException. Unless it is one we know we should not
461     // retry, we return true and try a new route.
462     return true;
463   }
464 
465   /**
466    * Report and attempt to recover from a failure to communicate with a server. Returns a new
467    * HTTP engine that should be used for the retry if {@code e} is recoverable, or null if
468    * the failure is permanent. Requests with a body can only be recovered if the
469    * body is buffered.
470    */
recover(IOException e, Sink requestBodyOut)471   public HttpEngine recover(IOException e, Sink requestBodyOut) {
472     if (routeSelector != null && connection != null) {
473       connectFailed(routeSelector, e);
474     }
475 
476     boolean canRetryRequestBody = requestBodyOut == null || requestBodyOut instanceof RetryableSink;
477     if (routeSelector == null && connection == null // No connection.
478         || routeSelector != null && !routeSelector.hasNext() // No more routes to attempt.
479         || !isRecoverable(e)
480         || !canRetryRequestBody) {
481       return null;
482     }
483 
484     Connection connection = close();
485 
486     // For failure recovery, use the same route selector with a new connection.
487     return new HttpEngine(client, userRequest, bufferRequestBody, callerWritesRequestBody,
488         forWebSocket, connection, routeSelector, (RetryableSink) requestBodyOut, priorResponse);
489   }
490 
connectFailed(RouteSelector routeSelector, IOException e)491   private void connectFailed(RouteSelector routeSelector, IOException e) {
492     // If this is a recycled connection, don't count its failure against the route.
493     if (Internal.instance.recycleCount(connection) > 0) return;
494     Route failedRoute = connection.getRoute();
495     routeSelector.connectFailed(failedRoute, e);
496   }
497 
recover(IOException e)498   public HttpEngine recover(IOException e) {
499     return recover(e, requestBodyOut);
500   }
501 
isRecoverable(IOException e)502   private boolean isRecoverable(IOException e) {
503     // If the application has opted-out of recovery, don't recover.
504     if (!client.getRetryOnConnectionFailure()) {
505       return false;
506     }
507 
508     // If there was a protocol problem, don't recover.
509     if (e instanceof ProtocolException) {
510       return false;
511     }
512 
513     // If there was an interruption or timeout, don't recover.
514     if (e instanceof InterruptedIOException) {
515       return false;
516     }
517 
518     return true;
519   }
520 
521   /**
522    * Returns the route used to retrieve the response. Null if we haven't
523    * connected yet, or if no connection was necessary.
524    */
getRoute()525   public Route getRoute() {
526     return route;
527   }
528 
maybeCache()529   private void maybeCache() throws IOException {
530     InternalCache responseCache = Internal.instance.internalCache(client);
531     if (responseCache == null) return;
532 
533     // Should we cache this response for this request?
534     if (!CacheStrategy.isCacheable(userResponse, networkRequest)) {
535       if (HttpMethod.invalidatesCache(networkRequest.method())) {
536         try {
537           responseCache.remove(networkRequest);
538         } catch (IOException ignored) {
539           // The cache cannot be written.
540         }
541       }
542       return;
543     }
544 
545     // Offer this request to the cache.
546     storeRequest = responseCache.put(stripBody(userResponse));
547   }
548 
549   /**
550    * Configure the socket connection to be either pooled or closed when it is
551    * either exhausted or closed. If it is unneeded when this is called, it will
552    * be released immediately.
553    */
releaseConnection()554   public void releaseConnection() throws IOException {
555     if (transport != null && connection != null) {
556       transport.releaseConnectionOnIdle();
557     }
558     connection = null;
559   }
560 
561   /**
562    * Immediately closes the socket connection if it's currently held by this engine. Use this to
563    * interrupt an in-flight request from any thread. It's the caller's responsibility to close the
564    * request body and response body streams; otherwise resources may be leaked.
565    *
566    * <p>This method is safe to be called concurrently, but provides limited guarantees. If a
567    * transport layer connection has been established (such as a HTTP/2 stream) that is terminated.
568    * Otherwise if a socket connection is being established, that is terminated.
569    */
disconnect()570   public void disconnect() {
571     try {
572       if (transport != null) {
573         transport.disconnect(this);
574       } else {
575         Connection connection = this.connection;
576         if (connection != null) {
577           Internal.instance.closeIfOwnedBy(connection, this);
578         }
579       }
580     } catch (IOException ignored) {
581     }
582   }
583 
584   /**
585    * Release any resources held by this engine. If a connection is still held by
586    * this engine, it is returned.
587    */
close()588   public Connection close() {
589     if (bufferedRequestBody != null) {
590       // This also closes the wrapped requestBodyOut.
591       closeQuietly(bufferedRequestBody);
592     } else if (requestBodyOut != null) {
593       closeQuietly(requestBodyOut);
594     }
595 
596     // If this engine never achieved a response body, its connection cannot be reused.
597     if (userResponse == null) {
598       if (connection != null) closeQuietly(connection.getSocket()); // TODO: does this break SPDY?
599       connection = null;
600       return null;
601     }
602 
603     // Close the response body. This will recycle the connection if it is eligible.
604     closeQuietly(userResponse.body());
605 
606     // Close the connection if it cannot be reused.
607     if (transport != null && connection != null && !transport.canReuseConnection()) {
608       closeQuietly(connection.getSocket());
609       connection = null;
610       return null;
611     }
612 
613     // Prevent this engine from disconnecting a connection it no longer owns.
614     if (connection != null && !Internal.instance.clearOwner(connection)) {
615       connection = null;
616     }
617 
618     Connection result = connection;
619     connection = null;
620     return result;
621   }
622 
623   /**
624    * Returns a new response that does gzip decompression on {@code response}, if transparent gzip
625    * was both offered by OkHttp and used by the origin server.
626    *
627    * <p>In addition to decompression, this will also strip the corresponding headers. We strip the
628    * Content-Encoding header to prevent the application from attempting to double decompress. We
629    * strip the Content-Length header because it is the length of the compressed content, but the
630    * application is only interested in the length of the uncompressed content.
631    *
632    * <p>This method should only be used for non-empty response bodies. Response codes like "304 Not
633    * Modified" can include "Content-Encoding: gzip" without a response body and we will crash if we
634    * attempt to decompress the zero-byte source.
635    */
unzip(final Response response)636   private Response unzip(final Response response) throws IOException {
637     if (!transparentGzip || !"gzip".equalsIgnoreCase(userResponse.header("Content-Encoding"))) {
638       return response;
639     }
640 
641     if (response.body() == null) {
642       return response;
643     }
644 
645     GzipSource responseBody = new GzipSource(response.body().source());
646     Headers strippedHeaders = response.headers().newBuilder()
647         .removeAll("Content-Encoding")
648         .removeAll("Content-Length")
649         .build();
650     return response.newBuilder()
651         .headers(strippedHeaders)
652         .body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)))
653         .build();
654   }
655 
656   /**
657    * Returns true if the response must have a (possibly 0-length) body.
658    * See RFC 2616 section 4.3.
659    */
hasBody(Response response)660   public static boolean hasBody(Response response) {
661     // HEAD requests never yield a body regardless of the response headers.
662     if (response.request().method().equals("HEAD")) {
663       return false;
664     }
665 
666     int responseCode = response.code();
667     if ((responseCode < HTTP_CONTINUE || responseCode >= 200)
668         && responseCode != HTTP_NO_CONTENT
669         && responseCode != HTTP_NOT_MODIFIED) {
670       return true;
671     }
672 
673     // If the Content-Length or Transfer-Encoding headers disagree with the
674     // response code, the response is malformed. For best compatibility, we
675     // honor the headers.
676     if (OkHeaders.contentLength(response) != -1
677         || "chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
678       return true;
679     }
680 
681     return false;
682   }
683 
684   /**
685    * Populates request with defaults and cookies.
686    *
687    * <p>This client doesn't specify a default {@code Accept} header because it
688    * doesn't know what content types the application is interested in.
689    */
networkRequest(Request request)690   private Request networkRequest(Request request) throws IOException {
691     Request.Builder result = request.newBuilder();
692 
693     if (request.header("Host") == null) {
694       result.header("Host", Util.hostHeader(request.httpUrl()));
695     }
696 
697     if ((connection == null || connection.getProtocol() != Protocol.HTTP_1_0)
698         && request.header("Connection") == null) {
699       result.header("Connection", "Keep-Alive");
700     }
701 
702     if (request.header("Accept-Encoding") == null) {
703       transparentGzip = true;
704       result.header("Accept-Encoding", "gzip");
705     }
706 
707     CookieHandler cookieHandler = client.getCookieHandler();
708     if (cookieHandler != null) {
709       // Capture the request headers added so far so that they can be offered to the CookieHandler.
710       // This is mostly to stay close to the RI; it is unlikely any of the headers above would
711       // affect cookie choice besides "Host".
712       Map<String, List<String>> headers = OkHeaders.toMultimap(result.build().headers(), null);
713 
714       Map<String, List<String>> cookies = cookieHandler.get(request.uri(), headers);
715 
716       // Add any new cookies to the request.
717       OkHeaders.addCookies(result, cookies);
718     }
719 
720     if (request.header("User-Agent") == null) {
721       result.header("User-Agent", Version.userAgent());
722     }
723 
724     return result.build();
725   }
726 
727   /**
728    * Flushes the remaining request header and body, parses the HTTP response
729    * headers and starts reading the HTTP response body if it exists.
730    */
readResponse()731   public void readResponse() throws IOException {
732     if (userResponse != null) {
733       return; // Already ready.
734     }
735     if (networkRequest == null && cacheResponse == null) {
736       throw new IllegalStateException("call sendRequest() first!");
737     }
738     if (networkRequest == null) {
739       return; // No network response to read.
740     }
741 
742     Response networkResponse;
743 
744     if (forWebSocket) {
745       transport.writeRequestHeaders(networkRequest);
746       networkResponse = readNetworkResponse();
747 
748     } else if (!callerWritesRequestBody) {
749       networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest);
750 
751     } else {
752       // Emit the request body's buffer so that everything is in requestBodyOut.
753       if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) {
754         bufferedRequestBody.emit();
755       }
756 
757       // Emit the request headers if we haven't yet. We might have just learned the Content-Length.
758       if (sentRequestMillis == -1) {
759         if (OkHeaders.contentLength(networkRequest) == -1
760             && requestBodyOut instanceof RetryableSink) {
761           long contentLength = ((RetryableSink) requestBodyOut).contentLength();
762           networkRequest = networkRequest.newBuilder()
763               .header("Content-Length", Long.toString(contentLength))
764               .build();
765         }
766         transport.writeRequestHeaders(networkRequest);
767       }
768 
769       // Write the request body to the socket.
770       if (requestBodyOut != null) {
771         if (bufferedRequestBody != null) {
772           // This also closes the wrapped requestBodyOut.
773           bufferedRequestBody.close();
774         } else {
775           requestBodyOut.close();
776         }
777         if (requestBodyOut instanceof RetryableSink) {
778           transport.writeRequestBody((RetryableSink) requestBodyOut);
779         }
780       }
781 
782       networkResponse = readNetworkResponse();
783     }
784 
785     receiveHeaders(networkResponse.headers());
786 
787     // If we have a cache response too, then we're doing a conditional get.
788     if (cacheResponse != null) {
789       if (validate(cacheResponse, networkResponse)) {
790         userResponse = cacheResponse.newBuilder()
791             .request(userRequest)
792             .priorResponse(stripBody(priorResponse))
793             .headers(combine(cacheResponse.headers(), networkResponse.headers()))
794             .cacheResponse(stripBody(cacheResponse))
795             .networkResponse(stripBody(networkResponse))
796             .build();
797         networkResponse.body().close();
798         releaseConnection();
799 
800         // Update the cache after combining headers but before stripping the
801         // Content-Encoding header (as performed by initContentStream()).
802         InternalCache responseCache = Internal.instance.internalCache(client);
803         responseCache.trackConditionalCacheHit();
804         responseCache.update(cacheResponse, stripBody(userResponse));
805         userResponse = unzip(userResponse);
806         return;
807       } else {
808         closeQuietly(cacheResponse.body());
809       }
810     }
811 
812     userResponse = networkResponse.newBuilder()
813         .request(userRequest)
814         .priorResponse(stripBody(priorResponse))
815         .cacheResponse(stripBody(cacheResponse))
816         .networkResponse(stripBody(networkResponse))
817         .build();
818 
819     if (hasBody(userResponse)) {
820       maybeCache();
821       userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));
822     }
823   }
824 
825   class NetworkInterceptorChain implements Interceptor.Chain {
826     private final int index;
827     private final Request request;
828     private int calls;
829 
NetworkInterceptorChain(int index, Request request)830     NetworkInterceptorChain(int index, Request request) {
831       this.index = index;
832       this.request = request;
833     }
834 
connection()835     @Override public Connection connection() {
836       return connection;
837     }
838 
request()839     @Override public Request request() {
840       return request;
841     }
842 
proceed(Request request)843     @Override public Response proceed(Request request) throws IOException {
844       calls++;
845 
846       if (index > 0) {
847         Interceptor caller = client.networkInterceptors().get(index - 1);
848         Address address = connection().getRoute().getAddress();
849 
850         // Confirm that the interceptor uses the connection we've already prepared.
851         if (!request.httpUrl().rfc2732host().equals(address.getRfc2732Host())
852             || request.httpUrl().port() != address.getUriPort()) {
853           throw new IllegalStateException("network interceptor " + caller
854               + " must retain the same host and port");
855         }
856 
857         // Confirm that this is the interceptor's first call to chain.proceed().
858         if (calls > 1) {
859           throw new IllegalStateException("network interceptor " + caller
860               + " must call proceed() exactly once");
861         }
862       }
863 
864       if (index < client.networkInterceptors().size()) {
865         // There's another interceptor in the chain. Call that.
866         NetworkInterceptorChain chain = new NetworkInterceptorChain(index + 1, request);
867         Interceptor interceptor = client.networkInterceptors().get(index);
868         Response interceptedResponse = interceptor.intercept(chain);
869 
870         // Confirm that the interceptor made the required call to chain.proceed().
871         if (chain.calls != 1) {
872           throw new IllegalStateException("network interceptor " + interceptor
873               + " must call proceed() exactly once");
874         }
875 
876         return interceptedResponse;
877       }
878 
879       transport.writeRequestHeaders(request);
880 
881       //Update the networkRequest with the possibly updated interceptor request.
882       networkRequest = request;
883 
884       if (permitsRequestBody() && request.body() != null) {
885         Sink requestBodyOut = transport.createRequestBody(request, request.body().contentLength());
886         BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
887         request.body().writeTo(bufferedRequestBody);
888         bufferedRequestBody.close();
889       }
890 
891       Response response = readNetworkResponse();
892 
893       int code = response.code();
894       if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
895         throw new ProtocolException(
896             "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
897       }
898 
899       return response;
900     }
901   }
902 
readNetworkResponse()903   private Response readNetworkResponse() throws IOException {
904     transport.finishRequest();
905 
906     Response networkResponse = transport.readResponseHeaders()
907         .request(networkRequest)
908         .handshake(connection.getHandshake())
909         .header(OkHeaders.SENT_MILLIS, Long.toString(sentRequestMillis))
910         .header(OkHeaders.RECEIVED_MILLIS, Long.toString(System.currentTimeMillis()))
911         .build();
912 
913     if (!forWebSocket) {
914       networkResponse = networkResponse.newBuilder()
915           .body(transport.openResponseBody(networkResponse))
916           .build();
917     }
918 
919     Internal.instance.setProtocol(connection, networkResponse.protocol());
920     return networkResponse;
921   }
922 
923   /**
924    * Returns a new source that writes bytes to {@code cacheRequest} as they are read by the source
925    * consumer. This is careful to discard bytes left over when the stream is closed; otherwise we
926    * may never exhaust the source stream and therefore not complete the cached response.
927    */
cacheWritingResponse(final CacheRequest cacheRequest, Response response)928   private Response cacheWritingResponse(final CacheRequest cacheRequest, Response response)
929       throws IOException {
930     // Some apps return a null body; for compatibility we treat that like a null cache request.
931     if (cacheRequest == null) return response;
932     Sink cacheBodyUnbuffered = cacheRequest.body();
933     if (cacheBodyUnbuffered == null) return response;
934 
935     final BufferedSource source = response.body().source();
936     final BufferedSink cacheBody = Okio.buffer(cacheBodyUnbuffered);
937 
938     Source cacheWritingSource = new Source() {
939       boolean cacheRequestClosed;
940 
941       @Override public long read(Buffer sink, long byteCount) throws IOException {
942         long bytesRead;
943         try {
944           bytesRead = source.read(sink, byteCount);
945         } catch (IOException e) {
946           if (!cacheRequestClosed) {
947             cacheRequestClosed = true;
948             cacheRequest.abort(); // Failed to write a complete cache response.
949           }
950           throw e;
951         }
952 
953         if (bytesRead == -1) {
954           if (!cacheRequestClosed) {
955             cacheRequestClosed = true;
956             cacheBody.close(); // The cache response is complete!
957           }
958           return -1;
959         }
960 
961         sink.copyTo(cacheBody.buffer(), sink.size() - bytesRead, bytesRead);
962         cacheBody.emitCompleteSegments();
963         return bytesRead;
964       }
965 
966       @Override public Timeout timeout() {
967         return source.timeout();
968       }
969 
970       @Override public void close() throws IOException {
971         if (!cacheRequestClosed
972             && !Util.discard(this, Transport.DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
973           cacheRequestClosed = true;
974           cacheRequest.abort();
975         }
976         source.close();
977       }
978     };
979 
980     return response.newBuilder()
981         .body(new RealResponseBody(response.headers(), Okio.buffer(cacheWritingSource)))
982         .build();
983   }
984 
985   /**
986    * Returns true if {@code cached} should be used; false if {@code network}
987    * response should be used.
988    */
validate(Response cached, Response network)989   private static boolean validate(Response cached, Response network) {
990     if (network.code() == HTTP_NOT_MODIFIED) {
991       return true;
992     }
993 
994     // The HTTP spec says that if the network's response is older than our
995     // cached response, we may return the cache's response. Like Chrome (but
996     // unlike Firefox), this client prefers to return the newer response.
997     Date lastModified = cached.headers().getDate("Last-Modified");
998     if (lastModified != null) {
999       Date networkLastModified = network.headers().getDate("Last-Modified");
1000       if (networkLastModified != null
1001           && networkLastModified.getTime() < lastModified.getTime()) {
1002         return true;
1003       }
1004     }
1005 
1006     return false;
1007   }
1008 
1009   /**
1010    * Combines cached headers with a network headers as defined by RFC 2616,
1011    * 13.5.3.
1012    */
combine(Headers cachedHeaders, Headers networkHeaders)1013   private static Headers combine(Headers cachedHeaders, Headers networkHeaders) throws IOException {
1014     Headers.Builder result = new Headers.Builder();
1015 
1016     for (int i = 0, size = cachedHeaders.size(); i < size; i++) {
1017       String fieldName = cachedHeaders.name(i);
1018       String value = cachedHeaders.value(i);
1019       if ("Warning".equalsIgnoreCase(fieldName) && value.startsWith("1")) {
1020         continue; // Drop 100-level freshness warnings.
1021       }
1022       if (!OkHeaders.isEndToEnd(fieldName) || networkHeaders.get(fieldName) == null) {
1023         result.add(fieldName, value);
1024       }
1025     }
1026 
1027     for (int i = 0, size = networkHeaders.size(); i < size; i++) {
1028       String fieldName = networkHeaders.name(i);
1029       if ("Content-Length".equalsIgnoreCase(fieldName)) {
1030         continue; // Ignore content-length headers of validating responses.
1031       }
1032       if (OkHeaders.isEndToEnd(fieldName)) {
1033         result.add(fieldName, networkHeaders.value(i));
1034       }
1035     }
1036 
1037     return result.build();
1038   }
1039 
receiveHeaders(Headers headers)1040   public void receiveHeaders(Headers headers) throws IOException {
1041     CookieHandler cookieHandler = client.getCookieHandler();
1042     if (cookieHandler != null) {
1043       cookieHandler.put(userRequest.uri(), OkHeaders.toMultimap(headers, null));
1044     }
1045   }
1046 
1047   /**
1048    * Figures out the HTTP request to make in response to receiving this engine's
1049    * response. This will either add authentication headers or follow redirects.
1050    * If a follow-up is either unnecessary or not applicable, this returns null.
1051    */
followUpRequest()1052   public Request followUpRequest() throws IOException {
1053     if (userResponse == null) throw new IllegalStateException();
1054     Proxy selectedProxy = getRoute() != null
1055         ? getRoute().getProxy()
1056         : client.getProxy();
1057     int responseCode = userResponse.code();
1058 
1059     switch (responseCode) {
1060       case HTTP_PROXY_AUTH:
1061         if (selectedProxy.type() != Proxy.Type.HTTP) {
1062           throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
1063         }
1064         // fall-through
1065       case HTTP_UNAUTHORIZED:
1066         return OkHeaders.processAuthHeader(client.getAuthenticator(), userResponse, selectedProxy);
1067 
1068       case HTTP_PERM_REDIRECT:
1069       case HTTP_TEMP_REDIRECT:
1070         // "If the 307 or 308 status code is received in response to a request other than GET
1071         // or HEAD, the user agent MUST NOT automatically redirect the request"
1072         if (!userRequest.method().equals("GET") && !userRequest.method().equals("HEAD")) {
1073             return null;
1074         }
1075         // fall-through
1076       case HTTP_MULT_CHOICE:
1077       case HTTP_MOVED_PERM:
1078       case HTTP_MOVED_TEMP:
1079       case HTTP_SEE_OTHER:
1080         // Does the client allow redirects?
1081         if (!client.getFollowRedirects()) return null;
1082 
1083         String location = userResponse.header("Location");
1084         if (location == null) return null;
1085         HttpUrl url = userRequest.httpUrl().resolve(location);
1086 
1087         // Don't follow redirects to unsupported protocols.
1088         if (url == null) return null;
1089 
1090         // If configured, don't follow redirects between SSL and non-SSL.
1091         boolean sameScheme = url.scheme().equals(userRequest.httpUrl().scheme());
1092         if (!sameScheme && !client.getFollowSslRedirects()) return null;
1093 
1094         // Redirects don't include a request body.
1095         Request.Builder requestBuilder = userRequest.newBuilder();
1096         if (HttpMethod.permitsRequestBody(userRequest.method())) {
1097           requestBuilder.method("GET", null);
1098           requestBuilder.removeHeader("Transfer-Encoding");
1099           requestBuilder.removeHeader("Content-Length");
1100           requestBuilder.removeHeader("Content-Type");
1101         }
1102 
1103         // When redirecting across hosts, drop all authentication headers. This
1104         // is potentially annoying to the application layer since they have no
1105         // way to retain them.
1106         if (!sameConnection(url)) {
1107           requestBuilder.removeHeader("Authorization");
1108         }
1109 
1110         return requestBuilder.url(url).build();
1111 
1112       default:
1113         return null;
1114     }
1115   }
1116 
1117   /**
1118    * Returns true if an HTTP request for {@code followUp} can reuse the
1119    * connection used by this engine.
1120    */
sameConnection(HttpUrl followUp)1121   public boolean sameConnection(HttpUrl followUp) {
1122     HttpUrl url = userRequest.httpUrl();
1123     return url.host().equals(followUp.host())
1124         && url.port() == followUp.port()
1125         && url.scheme().equals(followUp.scheme());
1126   }
1127 
createAddress(OkHttpClient client, Request request)1128   private static Address createAddress(OkHttpClient client, Request request) {
1129     SSLSocketFactory sslSocketFactory = null;
1130     HostnameVerifier hostnameVerifier = null;
1131     CertificatePinner certificatePinner = null;
1132     if (request.isHttps()) {
1133       sslSocketFactory = client.getSslSocketFactory();
1134       hostnameVerifier = client.getHostnameVerifier();
1135       certificatePinner = client.getCertificatePinner();
1136     }
1137 
1138     return new Address(request.httpUrl().rfc2732host(), request.httpUrl().port(),
1139         client.getSocketFactory(), sslSocketFactory, hostnameVerifier, certificatePinner,
1140         client.getAuthenticator(), client.getProxy(), client.getProtocols(),
1141         client.getConnectionSpecs(), client.getProxySelector());
1142   }
1143 }
1144