HttpEngine.java revision b11e44a402eb0f9c5da1f7482fc1eabdd9adff47
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 18package libcore.net.http; 19 20import java.io.BufferedOutputStream; 21import java.io.ByteArrayInputStream; 22import java.io.IOException; 23import java.io.InputStream; 24import java.io.OutputStream; 25import java.net.CacheRequest; 26import java.net.CacheResponse; 27import java.net.CookieHandler; 28import java.net.ExtendedResponseCache; 29import java.net.HttpURLConnection; 30import java.net.Proxy; 31import java.net.ResponseCache; 32import java.net.ResponseSource; 33import java.net.URI; 34import java.net.URISyntaxException; 35import java.net.URL; 36import java.nio.charset.Charsets; 37import java.util.Collections; 38import java.util.Date; 39import java.util.HashMap; 40import java.util.List; 41import java.util.Map; 42import java.util.zip.GZIPInputStream; 43import javax.net.ssl.SSLSocketFactory; 44import libcore.io.IoUtils; 45import libcore.io.Streams; 46import libcore.util.EmptyArray; 47 48/** 49 * Handles a single HTTP request/response pair. Each HTTP engine follows this 50 * lifecycle: 51 * <ol> 52 * <li>It is created. 53 * <li>The HTTP request message is sent with sendRequest(). Once the request 54 * is sent it is an error to modify the request headers. After 55 * sendRequest() has been called the request body can be written to if 56 * it exists. 57 * <li>The HTTP response message is read with readResponse(). After the 58 * response has been read the response headers and body can be read. 59 * All responses have a response body input stream, though in some 60 * instances this stream is empty. 61 * </ol> 62 * 63 * <p>The request and response may be served by the HTTP response cache, by the 64 * network, or by both in the event of a conditional GET. 65 * 66 * <p>This class may hold a socket connection that needs to be released or 67 * recycled. By default, this socket connection is held when the last byte of 68 * the response is consumed. To release the connection when it is no longer 69 * required, use {@link #automaticallyReleaseConnectionToPool()}. 70 */ 71public class HttpEngine { 72 private static final CacheResponse GATEWAY_TIMEOUT_RESPONSE = new CacheResponse() { 73 @Override public Map<String, List<String>> getHeaders() throws IOException { 74 Map<String, List<String>> result = new HashMap<String, List<String>>(); 75 result.put(null, Collections.singletonList("HTTP/1.1 504 Gateway Timeout")); 76 return result; 77 } 78 @Override public InputStream getBody() throws IOException { 79 return new ByteArrayInputStream(EmptyArray.BYTE); 80 } 81 }; 82 83 /** 84 * The maximum number of bytes to buffer when sending headers and a request 85 * body. When the headers and body can be sent in a single write, the 86 * request completes sooner. In one WiFi benchmark, using a large enough 87 * buffer sped up some uploads by half. 88 */ 89 private static final int MAX_REQUEST_BUFFER_LENGTH = 32768; 90 91 public static final int DEFAULT_CHUNK_LENGTH = 1024; 92 93 public static final String OPTIONS = "OPTIONS"; 94 public static final String GET = "GET"; 95 public static final String HEAD = "HEAD"; 96 public static final String POST = "POST"; 97 public static final String PUT = "PUT"; 98 public static final String DELETE = "DELETE"; 99 public static final String TRACE = "TRACE"; 100 public static final String CONNECT = "CONNECT"; 101 102 public static final int HTTP_CONTINUE = 100; 103 104 /** 105 * HTTP 1.1 doesn't specify how many redirects to follow, but HTTP/1.0 106 * recommended 5. http://www.w3.org/Protocols/HTTP/1.0/spec.html#Code3xx 107 */ 108 public static final int MAX_REDIRECTS = 5; 109 110 protected final HttpURLConnectionImpl policy; 111 112 protected final String method; 113 114 private ResponseSource responseSource; 115 116 protected HttpConnection connection; 117 private InputStream socketIn; 118 private OutputStream socketOut; 119 120 /** 121 * This stream buffers the request headers and the request body when their 122 * combined size is less than MAX_REQUEST_BUFFER_LENGTH. By combining them 123 * we can save socket writes, which in turn saves a packet transmission. 124 * This is socketOut if the request size is large or unknown. 125 */ 126 private OutputStream requestOut; 127 private AbstractHttpOutputStream requestBodyOut; 128 129 private InputStream responseBodyIn; 130 131 private final ResponseCache responseCache = ResponseCache.getDefault(); 132 private CacheResponse cacheResponse; 133 private CacheRequest cacheRequest; 134 135 /** The time when the request headers were written, or -1 if they haven't been written yet. */ 136 private long sentRequestMillis = -1; 137 138 /** 139 * True if this client added an "Accept-Encoding: gzip" header field and is 140 * therefore responsible for also decompressing the transfer stream. 141 */ 142 private boolean transparentGzip; 143 144 boolean sendChunked; 145 146 /** 147 * The version this client will use. Either 0 for HTTP/1.0, or 1 for 148 * HTTP/1.1. Upon receiving a non-HTTP/1.1 response, this client 149 * automatically sets its version to HTTP/1.0. 150 */ 151 // TODO: is HTTP minor version tracked across HttpEngines? 152 private int httpMinorVersion = 1; // Assume HTTP/1.1 153 154 private final URI uri; 155 156 private final RequestHeaders requestHeaders; 157 158 /** Null until a response is received from the network or the cache */ 159 private ResponseHeaders responseHeaders; 160 161 /* 162 * The cache response currently being validated on a conditional get. Null 163 * if the cached response doesn't exist or doesn't need validation. If the 164 * conditional get succeeds, these will be used for the response headers and 165 * body. If it fails, these be closed and set to null. 166 */ 167 private ResponseHeaders cachedResponseHeaders; 168 private InputStream cachedResponseBody; 169 170 /** 171 * True if the socket connection should be released to the connection pool 172 * when the response has been fully read. 173 */ 174 private boolean automaticallyReleaseConnectionToPool; 175 176 /** True if the socket connection is no longer needed by this engine. */ 177 private boolean connectionReleased; 178 179 /** 180 * @param requestHeaders the client's supplied request headers. This class 181 * creates a private copy that it can mutate. 182 * @param connection the connection used for an intermediate response 183 * immediately prior to this request/response pair, such as a same-host 184 * redirect. This engine assumes ownership of the connection and must 185 * release it when it is unneeded. 186 */ 187 public HttpEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders, 188 HttpConnection connection, RetryableOutputStream requestBodyOut) throws IOException { 189 this.policy = policy; 190 this.method = method; 191 this.connection = connection; 192 this.requestBodyOut = requestBodyOut; 193 194 try { 195 uri = policy.getURL().toURILenient(); 196 } catch (URISyntaxException e) { 197 throw new IOException(e); 198 } 199 200 this.requestHeaders = new RequestHeaders(uri, new RawHeaders(requestHeaders)); 201 } 202 203 public URI getUri() { 204 return uri; 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 public final void sendRequest() throws IOException { 213 if (responseSource != null) { 214 return; 215 } 216 217 prepareRawRequestHeaders(); 218 initResponseSource(); 219 if (responseCache instanceof ExtendedResponseCache) { 220 ((ExtendedResponseCache) responseCache).trackResponse(responseSource); 221 } 222 223 /* 224 * The raw response source may require the network, but the request 225 * headers may forbid network use. In that case, dispose of the network 226 * response and use a GATEWAY_TIMEOUT response instead, as specified 227 * by http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.4. 228 */ 229 if (requestHeaders.isOnlyIfCached() && responseSource.requiresConnection()) { 230 if (responseSource == ResponseSource.CONDITIONAL_CACHE) { 231 IoUtils.closeQuietly(cachedResponseBody); 232 } 233 this.responseSource = ResponseSource.CACHE; 234 this.cacheResponse = GATEWAY_TIMEOUT_RESPONSE; 235 RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(cacheResponse.getHeaders()); 236 setResponse(new ResponseHeaders(uri, rawResponseHeaders), cacheResponse.getBody()); 237 } 238 239 if (responseSource.requiresConnection()) { 240 sendSocketRequest(); 241 } else if (connection != null) { 242 HttpConnectionPool.INSTANCE.recycle(connection); 243 connection = null; 244 } 245 } 246 247 /** 248 * Initialize the source for this response. It may be corrected later if the 249 * request headers forbids network use. 250 */ 251 private void initResponseSource() throws IOException { 252 responseSource = ResponseSource.NETWORK; 253 if (!policy.getUseCaches() || responseCache == null) { 254 return; 255 } 256 257 CacheResponse candidate = responseCache.get(uri, method, 258 requestHeaders.getHeaders().toMultimap()); 259 if (candidate == null) { 260 return; 261 } 262 263 Map<String, List<String>> responseHeadersMap = candidate.getHeaders(); 264 cachedResponseBody = candidate.getBody(); 265 if (!acceptCacheResponseType(candidate) 266 || responseHeadersMap == null 267 || cachedResponseBody == null) { 268 IoUtils.closeQuietly(cachedResponseBody); 269 return; 270 } 271 272 RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(responseHeadersMap); 273 cachedResponseHeaders = new ResponseHeaders(uri, rawResponseHeaders); 274 long now = System.currentTimeMillis(); 275 this.responseSource = cachedResponseHeaders.chooseResponseSource(now, requestHeaders); 276 if (responseSource == ResponseSource.CACHE) { 277 this.cacheResponse = candidate; 278 setResponse(cachedResponseHeaders, cachedResponseBody); 279 } else if (responseSource == ResponseSource.CONDITIONAL_CACHE) { 280 this.cacheResponse = candidate; 281 } else if (responseSource == ResponseSource.NETWORK) { 282 IoUtils.closeQuietly(cachedResponseBody); 283 } else { 284 throw new AssertionError(); 285 } 286 } 287 288 private void sendSocketRequest() throws IOException { 289 if (connection == null) { 290 connect(); 291 } 292 293 if (socketOut != null || requestOut != null || socketIn != null) { 294 throw new IllegalStateException(); 295 } 296 297 socketOut = connection.getOutputStream(); 298 requestOut = socketOut; 299 socketIn = connection.getInputStream(); 300 301 if (hasRequestBody()) { 302 initRequestBodyOut(); 303 } 304 } 305 306 /** 307 * Connect to the origin server either directly or via a proxy. 308 */ 309 protected void connect() throws IOException { 310 if (connection == null) { 311 connection = openSocketConnection(); 312 } 313 } 314 315 protected final HttpConnection openSocketConnection() throws IOException { 316 HttpConnection result = HttpConnection.connect(uri, getSslSocketFactory(), 317 policy.getProxy(), requiresTunnel(), policy.getConnectTimeout()); 318 Proxy proxy = result.getAddress().getProxy(); 319 if (proxy != null) { 320 policy.setProxy(proxy); 321 } 322 result.setSoTimeout(policy.getReadTimeout()); 323 return result; 324 } 325 326 protected void initRequestBodyOut() throws IOException { 327 int chunkLength = policy.getChunkLength(); 328 if (chunkLength > 0 || requestHeaders.isChunked()) { 329 sendChunked = true; 330 if (chunkLength == -1) { 331 chunkLength = DEFAULT_CHUNK_LENGTH; 332 } 333 } 334 335 if (socketOut == null) { 336 throw new IllegalStateException("No socket to write to; was a POST cached?"); 337 } 338 339 if (httpMinorVersion == 0) { 340 sendChunked = false; 341 } 342 343 int fixedContentLength = policy.getFixedContentLength(); 344 if (requestBodyOut != null) { 345 // request body was already initialized by the predecessor HTTP engine 346 } else if (fixedContentLength != -1) { 347 writeRequestHeaders(fixedContentLength); 348 requestBodyOut = new FixedLengthOutputStream(requestOut, fixedContentLength); 349 } else if (sendChunked) { 350 writeRequestHeaders(-1); 351 requestBodyOut = new ChunkedOutputStream(requestOut, chunkLength); 352 } else if (requestHeaders.getContentLength() != -1) { 353 writeRequestHeaders(requestHeaders.getContentLength()); 354 requestBodyOut = new RetryableOutputStream(requestHeaders.getContentLength()); 355 } else { 356 requestBodyOut = new RetryableOutputStream(); 357 } 358 } 359 360 /** 361 * @param body the response body, or null if it doesn't exist or isn't 362 * available. 363 */ 364 private void setResponse(ResponseHeaders headers, InputStream body) throws IOException { 365 if (this.responseBodyIn != null) { 366 throw new IllegalStateException(); 367 } 368 this.responseHeaders = headers; 369 this.httpMinorVersion = responseHeaders.getHeaders().getHttpMinorVersion(); 370 if (body != null) { 371 initContentStream(body); 372 } 373 } 374 375 private boolean hasRequestBody() { 376 return method == POST || method == PUT; 377 } 378 379 /** 380 * Returns the request body or null if this request doesn't have a body. 381 */ 382 public final OutputStream getRequestBody() { 383 if (responseSource == null) { 384 throw new IllegalStateException(); 385 } 386 return requestBodyOut; 387 } 388 389 public final boolean hasResponse() { 390 return responseHeaders != null; 391 } 392 393 public final RequestHeaders getRequestHeaders() { 394 return requestHeaders; 395 } 396 397 public final ResponseHeaders getResponseHeaders() { 398 if (responseHeaders == null) { 399 throw new IllegalStateException(); 400 } 401 return responseHeaders; 402 } 403 404 public final int getResponseCode() { 405 if (responseHeaders == null) { 406 throw new IllegalStateException(); 407 } 408 return responseHeaders.getHeaders().getResponseCode(); 409 } 410 411 public final InputStream getResponseBody() { 412 if (responseHeaders == null) { 413 throw new IllegalStateException(); 414 } 415 return responseBodyIn; 416 } 417 418 public final CacheResponse getCacheResponse() { 419 return cacheResponse; 420 } 421 422 public final HttpConnection getConnection() { 423 return connection; 424 } 425 426 public final boolean hasRecycledConnection() { 427 return connection != null && connection.isRecycled(); 428 } 429 430 /** 431 * Returns true if {@code cacheResponse} is of the right type. This 432 * condition is necessary but not sufficient for the cached response to 433 * be used. 434 */ 435 protected boolean acceptCacheResponseType(CacheResponse cacheResponse) { 436 return true; 437 } 438 439 private void maybeCache() throws IOException { 440 // Never cache responses to proxy CONNECT requests. 441 if (method == CONNECT) { 442 return; 443 } 444 445 // Are we caching at all? 446 if (!policy.getUseCaches() || responseCache == null) { 447 return; 448 } 449 450 // Should we cache this response for this request? 451 if (!responseHeaders.isCacheable(requestHeaders)) { 452 return; 453 } 454 455 // Offer this request to the cache. 456 cacheRequest = responseCache.put(uri, getHttpConnectionToCache()); 457 } 458 459 protected HttpURLConnection getHttpConnectionToCache() { 460 return policy; 461 } 462 463 /** 464 * Cause the socket connection to be released to the connection pool when 465 * it is no longer needed. If it is already unneeded, it will be pooled 466 * immediately. 467 */ 468 public final void automaticallyReleaseConnectionToPool() { 469 automaticallyReleaseConnectionToPool = true; 470 if (connection != null && connectionReleased) { 471 HttpConnectionPool.INSTANCE.recycle(connection); 472 connection = null; 473 } 474 } 475 476 /** 477 * Releases this engine so that its resources may be either reused or 478 * closed. 479 */ 480 public final void release(boolean reusable) { 481 // If the response body comes from the cache, close it. 482 if (responseBodyIn == cachedResponseBody) { 483 IoUtils.closeQuietly(responseBodyIn); 484 } 485 486 if (!connectionReleased && connection != null) { 487 connectionReleased = true; 488 489 // We cannot reuse sockets that have incomplete output. 490 if (requestBodyOut != null && !requestBodyOut.closed) { 491 reusable = false; 492 } 493 494 // If the request specified that the connection shouldn't be reused, 495 // don't reuse it. This advice doesn't apply to CONNECT requests because 496 // the "Connection: close" header goes the origin server, not the proxy. 497 if (requestHeaders.hasConnectionClose() && method != CONNECT) { 498 reusable = false; 499 } 500 501 // If the response specified that the connection shouldn't be reused, don't reuse it. 502 if (responseHeaders != null && responseHeaders.hasConnectionClose()) { 503 reusable = false; 504 } 505 506 if (responseBodyIn instanceof UnknownLengthHttpInputStream) { 507 reusable = false; 508 } 509 510 if (reusable && responseBodyIn != null) { 511 // We must discard the response body before the connection can be reused. 512 try { 513 Streams.skipAll(responseBodyIn); 514 } catch (IOException e) { 515 reusable = false; 516 } 517 } 518 519 if (!reusable) { 520 connection.closeSocketAndStreams(); 521 connection = null; 522 } else if (automaticallyReleaseConnectionToPool) { 523 HttpConnectionPool.INSTANCE.recycle(connection); 524 connection = null; 525 } 526 } 527 } 528 529 private void initContentStream(InputStream transferStream) throws IOException { 530 if (transparentGzip && responseHeaders.isContentEncodingGzip()) { 531 /* 532 * If the response was transparently gzipped, remove the gzip header field 533 * so clients don't double decompress. http://b/3009828 534 * 535 * Also remove the Content-Length in this case because it contains the length 536 * of the gzipped response. This isn't terribly useful and is dangerous because 537 * clients can query the content length, but not the content encoding. 538 */ 539 responseHeaders.stripContentEncoding(); 540 responseHeaders.stripContentLength(); 541 responseBodyIn = new GZIPInputStream(transferStream); 542 } else { 543 responseBodyIn = transferStream; 544 } 545 } 546 547 private InputStream getTransferStream() throws IOException { 548 if (!hasResponseBody()) { 549 return new FixedLengthInputStream(socketIn, cacheRequest, this, 0); 550 } 551 552 if (responseHeaders.isChunked()) { 553 return new ChunkedInputStream(socketIn, cacheRequest, this); 554 } 555 556 if (responseHeaders.getContentLength() != -1) { 557 return new FixedLengthInputStream(socketIn, cacheRequest, this, 558 responseHeaders.getContentLength()); 559 } 560 561 /* 562 * Wrap the input stream from the HttpConnection (rather than 563 * just returning "socketIn" directly here), so that we can control 564 * its use after the reference escapes. 565 */ 566 return new UnknownLengthHttpInputStream(socketIn, cacheRequest, this); 567 } 568 569 private void readResponseHeaders() throws IOException { 570 RawHeaders headers; 571 do { 572 headers = new RawHeaders(); 573 headers.setStatusLine(Streams.readAsciiLine(socketIn)); 574 readHeaders(headers); 575 } while (headers.getResponseCode() == HTTP_CONTINUE); 576 setResponse(new ResponseHeaders(uri, headers), null); 577 } 578 579 /** 580 * Returns true if the response must have a (possibly 0-length) body. 581 * See RFC 2616 section 4.3. 582 */ 583 public final boolean hasResponseBody() { 584 int responseCode = responseHeaders.getHeaders().getResponseCode(); 585 586 // HEAD requests never yield a body regardless of the response headers. 587 if (method == HEAD) { 588 return false; 589 } 590 591 if (method != CONNECT 592 && (responseCode < HTTP_CONTINUE || responseCode >= 200) 593 && responseCode != HttpURLConnectionImpl.HTTP_NO_CONTENT 594 && responseCode != HttpURLConnectionImpl.HTTP_NOT_MODIFIED) { 595 return true; 596 } 597 598 /* 599 * If the Content-Length or Transfer-Encoding headers disagree with the 600 * response code, the response is malformed. For best compatibility, we 601 * honor the headers. 602 */ 603 if (responseHeaders.getContentLength() != -1 || responseHeaders.isChunked()) { 604 return true; 605 } 606 607 return false; 608 } 609 610 /** 611 * Trailers are headers included after the last chunk of a response encoded 612 * with chunked encoding. 613 */ 614 final void readTrailers() throws IOException { 615 readHeaders(responseHeaders.getHeaders()); 616 } 617 618 private void readHeaders(RawHeaders headers) throws IOException { 619 // parse the result headers until the first blank line 620 String line; 621 while (!(line = Streams.readAsciiLine(socketIn)).isEmpty()) { 622 headers.addLine(line); 623 } 624 625 CookieHandler cookieHandler = CookieHandler.getDefault(); 626 if (cookieHandler != null) { 627 cookieHandler.put(uri, headers.toMultimap()); 628 } 629 } 630 631 /** 632 * Prepares the HTTP headers and sends them to the server. 633 * 634 * <p>For streaming requests with a body, headers must be prepared 635 * <strong>before</strong> the output stream has been written to. Otherwise 636 * the body would need to be buffered! 637 * 638 * <p>For non-streaming requests with a body, headers must be prepared 639 * <strong>after</strong> the output stream has been written to and closed. 640 * This ensures that the {@code Content-Length} header field receives the 641 * proper value. 642 * 643 * @param contentLength the number of bytes in the request body, or -1 if 644 * the request body length is unknown. 645 */ 646 private void writeRequestHeaders(int contentLength) throws IOException { 647 if (sentRequestMillis != -1) { 648 throw new IllegalStateException(); 649 } 650 651 RawHeaders headersToSend = getNetworkRequestHeaders(); 652 byte[] bytes = headersToSend.toHeaderString().getBytes(Charsets.ISO_8859_1); 653 654 if (contentLength != -1 && bytes.length + contentLength <= MAX_REQUEST_BUFFER_LENGTH) { 655 requestOut = new BufferedOutputStream(socketOut, bytes.length + contentLength); 656 } 657 658 sentRequestMillis = System.currentTimeMillis(); 659 requestOut.write(bytes); 660 } 661 662 /** 663 * Returns the headers to send on a network request. 664 * 665 * <p>This adds the content length and content-type headers, which are 666 * neither needed nor known when querying the response cache. 667 * 668 * <p>It updates the status line, which may need to be fully qualified if 669 * the connection is using a proxy. 670 */ 671 protected RawHeaders getNetworkRequestHeaders() throws IOException { 672 requestHeaders.getHeaders().setStatusLine(getRequestLine()); 673 674 int fixedContentLength = policy.getFixedContentLength(); 675 if (fixedContentLength != -1) { 676 requestHeaders.setContentLength(fixedContentLength); 677 } else if (sendChunked) { 678 requestHeaders.setChunked(); 679 } else if (requestBodyOut instanceof RetryableOutputStream) { 680 int contentLength = ((RetryableOutputStream) requestBodyOut).contentLength(); 681 requestHeaders.setContentLength(contentLength); 682 } 683 684 return requestHeaders.getHeaders(); 685 } 686 687 /** 688 * Populates requestHeaders with defaults and cookies. 689 * 690 * <p>This client doesn't specify a default {@code Accept} header because it 691 * doesn't know what content types the application is interested in. 692 */ 693 private void prepareRawRequestHeaders() throws IOException { 694 requestHeaders.getHeaders().setStatusLine(getRequestLine()); 695 696 if (requestHeaders.getUserAgent() == null) { 697 requestHeaders.setUserAgent(getDefaultUserAgent()); 698 } 699 700 if (requestHeaders.getHost() == null) { 701 requestHeaders.setHost(getOriginAddress(policy.getURL())); 702 } 703 704 if (httpMinorVersion > 0 && requestHeaders.getConnection() == null) { 705 requestHeaders.setConnection("Keep-Alive"); 706 } 707 708 if (requestHeaders.getAcceptEncoding() == null) { 709 transparentGzip = true; 710 requestHeaders.setAcceptEncoding("gzip"); 711 } 712 713 if (hasRequestBody() && requestHeaders.getContentType() == null) { 714 requestHeaders.setContentType("application/x-www-form-urlencoded"); 715 } 716 717 long ifModifiedSince = policy.getIfModifiedSince(); 718 if (ifModifiedSince != 0) { 719 requestHeaders.setIfModifiedSince(new Date(ifModifiedSince)); 720 } 721 722 CookieHandler cookieHandler = CookieHandler.getDefault(); 723 if (cookieHandler != null) { 724 requestHeaders.addCookies( 725 cookieHandler.get(uri, requestHeaders.getHeaders().toMultimap())); 726 } 727 } 728 729 private String getRequestLine() { 730 String protocol = (httpMinorVersion == 0) ? "HTTP/1.0" : "HTTP/1.1"; 731 return method + " " + requestString() + " " + protocol; 732 } 733 734 private String requestString() { 735 URL url = policy.getURL(); 736 if (includeAuthorityInRequestLine()) { 737 return url.toString(); 738 } else { 739 String fileOnly = url.getFile(); 740 if (fileOnly == null) { 741 fileOnly = "/"; 742 } else if (!fileOnly.startsWith("/")) { 743 fileOnly = "/" + fileOnly; 744 } 745 return fileOnly; 746 } 747 } 748 749 /** 750 * Returns true if the request line should contain the full URL with host 751 * and port (like "GET http://android.com/foo HTTP/1.1") or only the path 752 * (like "GET /foo HTTP/1.1"). 753 * 754 * <p>This is non-final because for HTTPS it's never necessary to supply the 755 * full URL, even if a proxy is in use. 756 */ 757 protected boolean includeAuthorityInRequestLine() { 758 return policy.usingProxy(); 759 } 760 761 /** 762 * Returns the SSL configuration for connections created by this engine. 763 * We cannot reuse HTTPS connections if the socket factory has changed. 764 */ 765 protected SSLSocketFactory getSslSocketFactory() { 766 return null; 767 } 768 769 protected final String getDefaultUserAgent() { 770 String agent = System.getProperty("http.agent"); 771 return agent != null ? agent : ("Java" + System.getProperty("java.version")); 772 } 773 774 protected final String getOriginAddress(URL url) { 775 int port = url.getPort(); 776 String result = url.getHost(); 777 if (port > 0 && port != policy.getDefaultPort()) { 778 result = result + ":" + port; 779 } 780 return result; 781 } 782 783 protected boolean requiresTunnel() { 784 return false; 785 } 786 787 /** 788 * Flushes the remaining request header and body, parses the HTTP response 789 * headers and starts reading the HTTP response body if it exists. 790 */ 791 public final void readResponse() throws IOException { 792 if (hasResponse()) { 793 return; 794 } 795 796 if (responseSource == null) { 797 throw new IllegalStateException("readResponse() without sendRequest()"); 798 } 799 800 if (!responseSource.requiresConnection()) { 801 return; 802 } 803 804 if (sentRequestMillis == -1) { 805 int contentLength = requestBodyOut instanceof RetryableOutputStream 806 ? ((RetryableOutputStream) requestBodyOut).contentLength() 807 : -1; 808 writeRequestHeaders(contentLength); 809 } 810 811 if (requestBodyOut != null) { 812 requestBodyOut.close(); 813 if (requestBodyOut instanceof RetryableOutputStream) { 814 ((RetryableOutputStream) requestBodyOut).writeToSocket(requestOut); 815 } 816 } 817 818 requestOut.flush(); 819 requestOut = socketOut; 820 821 readResponseHeaders(); 822 responseHeaders.setLocalTimestamps(sentRequestMillis, System.currentTimeMillis()); 823 824 if (responseSource == ResponseSource.CONDITIONAL_CACHE) { 825 if (cachedResponseHeaders.validate(responseHeaders)) { 826 release(true); 827 ResponseHeaders combinedHeaders = cachedResponseHeaders.combine(responseHeaders); 828 setResponse(combinedHeaders, cachedResponseBody); 829 if (responseCache instanceof ExtendedResponseCache) { 830 ExtendedResponseCache httpResponseCache = (ExtendedResponseCache) responseCache; 831 httpResponseCache.trackConditionalCacheHit(); 832 httpResponseCache.update(cacheResponse, getHttpConnectionToCache()); 833 } 834 return; 835 } else { 836 IoUtils.closeQuietly(cachedResponseBody); 837 } 838 } 839 840 if (hasResponseBody()) { 841 maybeCache(); // reentrant. this calls into user code which may call back into this! 842 } 843 844 initContentStream(getTransferStream()); 845 } 846} 847