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.Connection; 22 import com.squareup.okhttp.Headers; 23 import com.squareup.okhttp.HostResolver; 24 import com.squareup.okhttp.OkHttpClient; 25 import com.squareup.okhttp.OkResponseCache; 26 import com.squareup.okhttp.Request; 27 import com.squareup.okhttp.Response; 28 import com.squareup.okhttp.ResponseSource; 29 import com.squareup.okhttp.Route; 30 import com.squareup.okhttp.TunnelRequest; 31 import java.io.IOException; 32 import java.io.InputStream; 33 import java.net.CacheRequest; 34 import java.net.CookieHandler; 35 import java.net.ProtocolException; 36 import java.net.URL; 37 import java.net.UnknownHostException; 38 import java.security.cert.CertificateException; 39 import java.util.List; 40 import java.util.Map; 41 import javax.net.ssl.HostnameVerifier; 42 import javax.net.ssl.SSLHandshakeException; 43 import javax.net.ssl.SSLSocketFactory; 44 import okio.BufferedSink; 45 import okio.GzipSource; 46 import okio.Okio; 47 import okio.Sink; 48 import okio.Source; 49 50 import static com.squareup.okhttp.internal.Util.closeQuietly; 51 import static com.squareup.okhttp.internal.Util.getDefaultPort; 52 import static com.squareup.okhttp.internal.Util.getEffectivePort; 53 import static com.squareup.okhttp.internal.http.StatusLine.HTTP_CONTINUE; 54 import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED; 55 import static java.net.HttpURLConnection.HTTP_NO_CONTENT; 56 57 /** 58 * Handles a single HTTP request/response pair. Each HTTP engine follows this 59 * lifecycle: 60 * <ol> 61 * <li>It is created. 62 * <li>The HTTP request message is sent with sendRequest(). Once the request 63 * is sent it is an error to modify the request headers. After 64 * sendRequest() has been called the request body can be written to if 65 * it exists. 66 * <li>The HTTP response message is read with readResponse(). After the 67 * response has been read the response headers and body can be read. 68 * All responses have a response body input stream, though in some 69 * instances this stream is empty. 70 * </ol> 71 * 72 * <p>The request and response may be served by the HTTP response cache, by the 73 * network, or by both in the event of a conditional GET. 74 */ 75 public class HttpEngine { 76 final OkHttpClient client; 77 78 private Connection connection; 79 private RouteSelector routeSelector; 80 private Route route; 81 private final Response priorResponse; 82 83 private Transport transport; 84 85 /** The time when the request headers were written, or -1 if they haven't been written yet. */ 86 long sentRequestMillis = -1; 87 88 /** 89 * True if this client added an "Accept-Encoding: gzip" header field and is 90 * therefore responsible for also decompressing the transfer stream. 91 */ 92 private boolean transparentGzip; 93 94 /** 95 * True if the request body must be completely buffered before transmission; 96 * false if it can be streamed. Buffering has two advantages: we don't need 97 * the content-length in advance and we can retransmit if necessary. The 98 * upside of streaming is that we can save memory. 99 */ 100 public final boolean bufferRequestBody; 101 102 /** 103 * The original application-provided request. Never modified by OkHttp. When 104 * follow-up requests are necessary, they are derived from this request. 105 */ 106 private final Request userRequest; 107 108 /** 109 * The request to send on the network, or null for no network request. This is 110 * derived from the user request, and customized to support OkHttp features 111 * like compression and caching. 112 */ 113 private Request networkRequest; 114 115 /** 116 * The cached response, or null if the cache doesn't exist or cannot be used 117 * for this request. Conditional caching means this may be non-null even when 118 * the network request is non-null. Never modified by OkHttp. 119 */ 120 private Response cacheResponse; 121 122 /** 123 * The response read from the network. Null if the network response hasn't 124 * been read yet, or if the network is not used. Never modified by OkHttp. 125 */ 126 private Response networkResponse; 127 128 /** 129 * The user-visible response. This is derived from either the network 130 * response, cache response, or both. It is customized to support OkHttp 131 * features like compression and caching. 132 */ 133 private Response userResponse; 134 135 private Sink requestBodyOut; 136 private BufferedSink bufferedRequestBody; 137 138 private ResponseSource responseSource; 139 140 /** Null until a response is received from the network or the cache. */ 141 private Source responseTransferSource; 142 private Source responseBody; 143 private InputStream responseBodyBytes; 144 145 /** The cache request currently being populated from a network response. */ 146 private CacheRequest storeRequest; 147 148 /** 149 * @param request the HTTP request without a body. The body must be 150 * written via the engine's request body stream. 151 * @param connection the connection used for an intermediate response 152 * immediately prior to this request/response pair, such as a same-host 153 * redirect. This engine assumes ownership of the connection and must 154 * release it when it is unneeded. 155 * @param routeSelector the route selector used for a failed attempt 156 * immediately preceding this attempt, or null if this request doesn't 157 * recover from a failure. 158 */ 159 public HttpEngine(OkHttpClient client, Request request, boolean bufferRequestBody, 160 Connection connection, RouteSelector routeSelector, RetryableSink requestBodyOut, 161 Response priorResponse) { 162 this.client = client; 163 this.userRequest = request; 164 this.bufferRequestBody = bufferRequestBody; 165 this.connection = connection; 166 this.routeSelector = routeSelector; 167 this.requestBodyOut = requestBodyOut; 168 this.priorResponse = priorResponse; 169 170 if (connection != null) { 171 connection.setOwner(this); 172 this.route = connection.getRoute(); 173 } else { 174 this.route = null; 175 } 176 } 177 178 /** 179 * Figures out what the response source will be, and opens a socket to that 180 * source if necessary. Prepares the request headers and gets ready to start 181 * writing the request body if it exists. 182 */ 183 public final void sendRequest() throws IOException { 184 if (responseSource != null) return; // Already sent. 185 if (transport != null) throw new IllegalStateException(); 186 187 Request request = networkRequest(userRequest); 188 189 OkResponseCache responseCache = client.getOkResponseCache(); 190 Response cacheCandidate = responseCache != null 191 ? responseCache.get(request) 192 : null; 193 long now = System.currentTimeMillis(); 194 CacheStrategy cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get(); 195 responseSource = cacheStrategy.source; 196 networkRequest = cacheStrategy.networkRequest; 197 cacheResponse = cacheStrategy.cacheResponse; 198 199 if (responseCache != null) { 200 responseCache.trackResponse(responseSource); 201 } 202 203 if (cacheCandidate != null 204 && (responseSource == ResponseSource.NONE || cacheResponse == null)) { 205 closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it. 206 } 207 208 if (networkRequest != null) { 209 // Open a connection unless we inherited one from a redirect. 210 if (connection == null) { 211 connect(networkRequest); 212 } 213 214 // Blow up if we aren't the current owner of the connection. 215 if (connection.getOwner() != this && !connection.isSpdy()) throw new AssertionError(); 216 217 transport = (Transport) connection.newTransport(this); 218 219 // Create a request body if we don't have one already. We'll already have 220 // one if we're retrying a failed POST. 221 if (hasRequestBody() && requestBodyOut == null) { 222 requestBodyOut = transport.createRequestBody(request); 223 } 224 225 } else { 226 // We're using a cached response. Recycle a connection we may have inherited from a redirect. 227 if (connection != null) { 228 client.getConnectionPool().recycle(connection); 229 connection = null; 230 } 231 232 // No need for the network! Promote the cached response immediately. 233 this.userResponse = cacheResponse.newBuilder() 234 .request(userRequest) 235 .priorResponse(stripBody(priorResponse)) 236 .cacheResponse(stripBody(cacheResponse)) 237 .build(); 238 if (userResponse.body() != null) { 239 initContentStream(userResponse.body().source()); 240 } 241 } 242 } 243 244 private static Response stripBody(Response response) { 245 return response != null && response.body() != null 246 ? response.newBuilder().body(null).build() 247 : response; 248 } 249 250 /** Connect to the origin server either directly or via a proxy. */ 251 private void connect(Request request) throws IOException { 252 if (connection != null) throw new IllegalStateException(); 253 254 if (routeSelector == null) { 255 String uriHost = request.url().getHost(); 256 if (uriHost == null || uriHost.length() == 0) { 257 throw new UnknownHostException(request.url().toString()); 258 } 259 SSLSocketFactory sslSocketFactory = null; 260 HostnameVerifier hostnameVerifier = null; 261 if (request.isHttps()) { 262 sslSocketFactory = client.getSslSocketFactory(); 263 hostnameVerifier = client.getHostnameVerifier(); 264 } 265 Address address = new Address(uriHost, getEffectivePort(request.url()), 266 client.getSocketFactory(), sslSocketFactory, hostnameVerifier, client.getAuthenticator(), 267 client.getProxy(), client.getProtocols()); 268 routeSelector = new RouteSelector(address, request.uri(), client.getProxySelector(), 269 client.getConnectionPool(), client.getHostResolver(), client.getRoutesDatabase()); 270 } 271 272 connection = routeSelector.next(request.method()); 273 connection.setOwner(this); 274 275 if (!connection.isConnected()) { 276 connection.connect(client.getConnectTimeout(), client.getReadTimeout(), getTunnelConfig()); 277 if (connection.isSpdy()) client.getConnectionPool().share(connection); 278 client.getRoutesDatabase().connected(connection.getRoute()); 279 } else if (!connection.isSpdy()) { 280 connection.updateReadTimeout(client.getReadTimeout()); 281 } 282 283 route = connection.getRoute(); 284 } 285 286 /** 287 * Called immediately before the transport transmits HTTP request headers. 288 * This is used to observe the sent time should the request be cached. 289 */ 290 public void writingRequestHeaders() { 291 if (sentRequestMillis != -1) throw new IllegalStateException(); 292 sentRequestMillis = System.currentTimeMillis(); 293 } 294 295 boolean hasRequestBody() { 296 return HttpMethod.hasRequestBody(userRequest.method()); 297 } 298 299 /** Returns the request body or null if this request doesn't have a body. */ 300 public final Sink getRequestBody() { 301 if (responseSource == null) throw new IllegalStateException(); 302 return requestBodyOut; 303 } 304 305 public final BufferedSink getBufferedRequestBody() { 306 BufferedSink result = bufferedRequestBody; 307 if (result != null) return result; 308 Sink requestBody = getRequestBody(); 309 return requestBody != null 310 ? (bufferedRequestBody = Okio.buffer(requestBody)) 311 : null; 312 } 313 314 public final boolean hasResponse() { 315 return userResponse != null; 316 } 317 318 public final Request getRequest() { 319 return userRequest; 320 } 321 322 /** Returns the engine's response. */ 323 // TODO: the returned body will always be null. 324 public final Response getResponse() { 325 if (userResponse == null) throw new IllegalStateException(); 326 return userResponse; 327 } 328 329 public final Source getResponseBody() { 330 if (userResponse == null) throw new IllegalStateException(); 331 return responseBody; 332 } 333 334 public final InputStream getResponseBodyBytes() { 335 InputStream result = responseBodyBytes; 336 return result != null 337 ? result 338 : (responseBodyBytes = Okio.buffer(getResponseBody()).inputStream()); 339 } 340 341 public final Connection getConnection() { 342 return connection; 343 } 344 345 /** 346 * Report and attempt to recover from {@code e}. Returns a new HTTP engine 347 * that should be used for the retry if {@code e} is recoverable, or null if 348 * the failure is permanent. 349 */ 350 public HttpEngine recover(IOException e) { 351 if (routeSelector != null && connection != null) { 352 routeSelector.connectFailed(connection, e); 353 } 354 355 boolean canRetryRequestBody = requestBodyOut == null || requestBodyOut instanceof RetryableSink; 356 if (routeSelector == null && connection == null // No connection. 357 || routeSelector != null && !routeSelector.hasNext() // No more routes to attempt. 358 || !isRecoverable(e) 359 || !canRetryRequestBody) { 360 return null; 361 } 362 363 Connection connection = close(); 364 365 // For failure recovery, use the same route selector with a new connection. 366 return new HttpEngine(client, userRequest, bufferRequestBody, connection, routeSelector, 367 (RetryableSink) requestBodyOut, priorResponse); 368 } 369 370 private boolean isRecoverable(IOException e) { 371 // If the problem was a CertificateException from the X509TrustManager, 372 // do not retry, we didn't have an abrupt server-initiated exception. 373 boolean sslFailure = 374 e instanceof SSLHandshakeException && e.getCause() instanceof CertificateException; 375 boolean protocolFailure = e instanceof ProtocolException; 376 return !sslFailure && !protocolFailure; 377 } 378 379 /** 380 * Returns the route used to retrieve the response. Null if we haven't 381 * connected yet, or if no connection was necessary. 382 */ 383 public Route getRoute() { 384 return route; 385 } 386 387 private void maybeCache() throws IOException { 388 OkResponseCache responseCache = client.getOkResponseCache(); 389 if (responseCache == null) return; 390 391 // Should we cache this response for this request? 392 if (!CacheStrategy.isCacheable(userResponse, networkRequest)) { 393 responseCache.maybeRemove(networkRequest); 394 return; 395 } 396 397 // Offer this request to the cache. 398 storeRequest = responseCache.put(stripBody(userResponse)); 399 } 400 401 /** 402 * Configure the socket connection to be either pooled or closed when it is 403 * either exhausted or closed. If it is unneeded when this is called, it will 404 * be released immediately. 405 */ 406 public final void releaseConnection() throws IOException { 407 if (transport != null && connection != null) { 408 transport.releaseConnectionOnIdle(); 409 } 410 connection = null; 411 } 412 413 /** 414 * Immediately closes the socket connection if it's currently held by this 415 * engine. Use this to interrupt an in-flight request from any thread. It's 416 * the caller's responsibility to close the request body and response body 417 * streams; otherwise resources may be leaked. 418 */ 419 public final void disconnect() throws IOException { 420 if (transport != null) { 421 transport.disconnect(this); 422 } 423 } 424 425 /** 426 * Release any resources held by this engine. If a connection is still held by 427 * this engine, it is returned. 428 */ 429 public final Connection close() { 430 if (bufferedRequestBody != null) { 431 // This also closes the wrapped requestBodyOut. 432 closeQuietly(bufferedRequestBody); 433 } else if (requestBodyOut != null) { 434 closeQuietly(requestBodyOut); 435 } 436 437 // If this engine never achieved a response body, its connection cannot be reused. 438 if (responseBody == null) { 439 closeQuietly(connection); 440 connection = null; 441 return null; 442 } 443 444 // Close the response body. This will recycle the connection if it is eligible. 445 closeQuietly(responseBody); 446 447 // Clear the buffer held by the response body input stream adapter. 448 closeQuietly(responseBodyBytes); 449 450 // Close the connection if it cannot be reused. 451 if (transport != null && !transport.canReuseConnection()) { 452 closeQuietly(connection); 453 connection = null; 454 return null; 455 } 456 457 // Prevent this engine from disconnecting a connection it no longer owns. 458 if (connection != null && !connection.clearOwner()) { 459 connection = null; 460 } 461 462 Connection result = connection; 463 connection = null; 464 return result; 465 } 466 467 /** 468 * Initialize the response content stream from the response transfer source. 469 * These two sources are the same unless we're doing transparent gzip, in 470 * which case the content source is decompressed. 471 * 472 * <p>Whenever we do transparent gzip we also strip the corresponding headers. 473 * We strip the Content-Encoding header to prevent the application from 474 * attempting to double decompress. We strip the Content-Length header because 475 * it is the length of the compressed content, but the application is only 476 * interested in the length of the uncompressed content. 477 * 478 * <p>This method should only be used for non-empty response bodies. Response 479 * codes like "304 Not Modified" can include "Content-Encoding: gzip" without 480 * a response body and we will crash if we attempt to decompress the zero-byte 481 * source. 482 */ 483 private void initContentStream(Source transferSource) throws IOException { 484 responseTransferSource = transferSource; 485 if (transparentGzip && "gzip".equalsIgnoreCase(userResponse.header("Content-Encoding"))) { 486 userResponse = userResponse.newBuilder() 487 .removeHeader("Content-Encoding") 488 .removeHeader("Content-Length") 489 .build(); 490 responseBody = new GzipSource(transferSource); 491 } else { 492 responseBody = transferSource; 493 } 494 } 495 496 /** 497 * Returns true if the response must have a (possibly 0-length) body. 498 * See RFC 2616 section 4.3. 499 */ 500 public final boolean hasResponseBody() { 501 // HEAD requests never yield a body regardless of the response headers. 502 if (userRequest.method().equals("HEAD")) { 503 return false; 504 } 505 506 int responseCode = userResponse.code(); 507 if ((responseCode < HTTP_CONTINUE || responseCode >= 200) 508 && responseCode != HTTP_NO_CONTENT 509 && responseCode != HTTP_NOT_MODIFIED) { 510 return true; 511 } 512 513 // If the Content-Length or Transfer-Encoding headers disagree with the 514 // response code, the response is malformed. For best compatibility, we 515 // honor the headers. 516 if (OkHeaders.contentLength(networkResponse) != -1 517 || "chunked".equalsIgnoreCase(networkResponse.header("Transfer-Encoding"))) { 518 return true; 519 } 520 521 return false; 522 } 523 524 /** 525 * Populates request with defaults and cookies. 526 * 527 * <p>This client doesn't specify a default {@code Accept} header because it 528 * doesn't know what content types the application is interested in. 529 */ 530 private Request networkRequest(Request request) throws IOException { 531 Request.Builder result = request.newBuilder(); 532 533 if (request.getUserAgent() == null) { 534 result.setUserAgent(getDefaultUserAgent()); 535 } 536 537 if (request.header("Host") == null) { 538 result.header("Host", hostHeader(request.url())); 539 } 540 541 if ((connection == null || connection.getHttpMinorVersion() != 0) 542 && request.header("Connection") == null) { 543 result.header("Connection", "Keep-Alive"); 544 } 545 546 if (request.header("Accept-Encoding") == null) { 547 transparentGzip = true; 548 result.header("Accept-Encoding", "gzip"); 549 } 550 551 if (hasRequestBody() && request.header("Content-Type") == null) { 552 result.header("Content-Type", "application/x-www-form-urlencoded"); 553 } 554 555 CookieHandler cookieHandler = client.getCookieHandler(); 556 if (cookieHandler != null) { 557 // Capture the request headers added so far so that they can be offered to the CookieHandler. 558 // This is mostly to stay close to the RI; it is unlikely any of the headers above would 559 // affect cookie choice besides "Host". 560 Map<String, List<String>> headers = OkHeaders.toMultimap(result.build().headers(), null); 561 562 Map<String, List<String>> cookies = cookieHandler.get(request.uri(), headers); 563 564 // Add any new cookies to the request. 565 OkHeaders.addCookies(result, cookies); 566 } 567 568 return result.build(); 569 } 570 571 public static String getDefaultUserAgent() { 572 String agent = System.getProperty("http.agent"); 573 return agent != null ? agent : ("Java" + System.getProperty("java.version")); 574 } 575 576 public static String hostHeader(URL url) { 577 return getEffectivePort(url) != getDefaultPort(url.getProtocol()) 578 ? url.getHost() + ":" + url.getPort() 579 : url.getHost(); 580 } 581 582 /** 583 * Flushes the remaining request header and body, parses the HTTP response 584 * headers and starts reading the HTTP response body if it exists. 585 */ 586 public final void readResponse() throws IOException { 587 if (userResponse != null) { 588 return; // Already ready. 589 } 590 if (networkRequest == null && cacheResponse == null) { 591 throw new IllegalStateException("call sendRequest() first!"); 592 } 593 if (networkRequest == null) { 594 return; // No network response to read. 595 } 596 597 // Flush the request body if there's data outstanding. 598 if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) { 599 bufferedRequestBody.flush(); 600 } 601 602 if (sentRequestMillis == -1) { 603 if (OkHeaders.contentLength(networkRequest) == -1 604 && requestBodyOut instanceof RetryableSink) { 605 // We might not learn the Content-Length until the request body has been buffered. 606 long contentLength = ((RetryableSink) requestBodyOut).contentLength(); 607 networkRequest = networkRequest.newBuilder() 608 .header("Content-Length", Long.toString(contentLength)) 609 .build(); 610 } 611 transport.writeRequestHeaders(networkRequest); 612 } 613 614 if (requestBodyOut != null) { 615 if (bufferedRequestBody != null) { 616 // This also closes the wrapped requestBodyOut. 617 bufferedRequestBody.close(); 618 } else { 619 requestBodyOut.close(); 620 } 621 if (requestBodyOut instanceof RetryableSink) { 622 transport.writeRequestBody((RetryableSink) requestBodyOut); 623 } 624 } 625 626 transport.flushRequest(); 627 628 networkResponse = transport.readResponseHeaders() 629 .request(networkRequest) 630 .handshake(connection.getHandshake()) 631 .header(OkHeaders.SENT_MILLIS, Long.toString(sentRequestMillis)) 632 .header(OkHeaders.RECEIVED_MILLIS, Long.toString(System.currentTimeMillis())) 633 .setResponseSource(responseSource) 634 .build(); 635 connection.setHttpMinorVersion(networkResponse.httpMinorVersion()); 636 receiveHeaders(networkResponse.headers()); 637 638 if (responseSource == ResponseSource.CONDITIONAL_CACHE) { 639 if (cacheResponse.validate(networkResponse)) { 640 userResponse = cacheResponse.newBuilder() 641 .request(userRequest) 642 .priorResponse(stripBody(priorResponse)) 643 .headers(combine(cacheResponse.headers(), networkResponse.headers())) 644 .cacheResponse(stripBody(cacheResponse)) 645 .networkResponse(stripBody(networkResponse)) 646 .build(); 647 transport.emptyTransferStream(); 648 releaseConnection(); 649 650 // Update the cache after combining headers but before stripping the 651 // Content-Encoding header (as performed by initContentStream()). 652 OkResponseCache responseCache = client.getOkResponseCache(); 653 responseCache.trackConditionalCacheHit(); 654 responseCache.update(cacheResponse, stripBody(userResponse)); 655 if (cacheResponse.body() != null) { 656 initContentStream(cacheResponse.body().source()); 657 } 658 659 return; 660 } else { 661 closeQuietly(cacheResponse.body()); 662 } 663 } 664 665 userResponse = networkResponse.newBuilder() 666 .request(userRequest) 667 .priorResponse(stripBody(priorResponse)) 668 .cacheResponse(stripBody(cacheResponse)) 669 .networkResponse(stripBody(networkResponse)) 670 .build(); 671 672 if (!hasResponseBody()) { 673 // Don't call initContentStream() when the response doesn't have any content. 674 responseTransferSource = transport.getTransferStream(storeRequest); 675 responseBody = responseTransferSource; 676 return; 677 } 678 679 maybeCache(); 680 initContentStream(transport.getTransferStream(storeRequest)); 681 } 682 683 /** 684 * Combines cached headers with a network headers as defined by RFC 2616, 685 * 13.5.3. 686 */ 687 private static Headers combine(Headers cachedHeaders, Headers networkHeaders) throws IOException { 688 Headers.Builder result = new Headers.Builder(); 689 690 for (int i = 0; i < cachedHeaders.size(); i++) { 691 String fieldName = cachedHeaders.name(i); 692 String value = cachedHeaders.value(i); 693 if ("Warning".equals(fieldName) && value.startsWith("1")) { 694 continue; // drop 100-level freshness warnings 695 } 696 if (!isEndToEnd(fieldName) || networkHeaders.get(fieldName) == null) { 697 result.add(fieldName, value); 698 } 699 } 700 701 for (int i = 0; i < networkHeaders.size(); i++) { 702 String fieldName = networkHeaders.name(i); 703 if (isEndToEnd(fieldName)) { 704 result.add(fieldName, networkHeaders.value(i)); 705 } 706 } 707 708 return result.build(); 709 } 710 711 /** 712 * Returns true if {@code fieldName} is an end-to-end HTTP header, as 713 * defined by RFC 2616, 13.5.1. 714 */ 715 private static boolean isEndToEnd(String fieldName) { 716 return !"Connection".equalsIgnoreCase(fieldName) 717 && !"Keep-Alive".equalsIgnoreCase(fieldName) 718 && !"Proxy-Authenticate".equalsIgnoreCase(fieldName) 719 && !"Proxy-Authorization".equalsIgnoreCase(fieldName) 720 && !"TE".equalsIgnoreCase(fieldName) 721 && !"Trailers".equalsIgnoreCase(fieldName) 722 && !"Transfer-Encoding".equalsIgnoreCase(fieldName) 723 && !"Upgrade".equalsIgnoreCase(fieldName); 724 } 725 726 private TunnelRequest getTunnelConfig() { 727 if (!userRequest.isHttps()) return null; 728 729 String userAgent = userRequest.getUserAgent(); 730 if (userAgent == null) userAgent = getDefaultUserAgent(); 731 732 URL url = userRequest.url(); 733 return new TunnelRequest(url.getHost(), getEffectivePort(url), userAgent, 734 userRequest.getProxyAuthorization()); 735 } 736 737 public void receiveHeaders(Headers headers) throws IOException { 738 CookieHandler cookieHandler = client.getCookieHandler(); 739 if (cookieHandler != null) { 740 cookieHandler.put(userRequest.uri(), OkHeaders.toMultimap(headers, null)); 741 } 742 } 743 } 744