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