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 libcore.net.http; 19 20 import java.io.BufferedOutputStream; 21 import java.io.ByteArrayInputStream; 22 import java.io.IOException; 23 import java.io.InputStream; 24 import java.io.OutputStream; 25 import java.net.CacheRequest; 26 import java.net.CacheResponse; 27 import java.net.CookieHandler; 28 import java.net.ExtendedResponseCache; 29 import java.net.HttpURLConnection; 30 import java.net.Proxy; 31 import java.net.ResponseCache; 32 import java.net.ResponseSource; 33 import java.net.URI; 34 import java.net.URISyntaxException; 35 import java.net.URL; 36 import java.nio.charset.Charsets; 37 import java.util.Collections; 38 import java.util.Date; 39 import java.util.HashMap; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.zip.GZIPInputStream; 43 import javax.net.ssl.SSLSocketFactory; 44 import libcore.io.IoUtils; 45 import libcore.io.Streams; 46 import 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 */ 71 public class HttpEngine { 72 private static final CacheResponse BAD_GATEWAY_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 502 Bad Gateway")); 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 BAD_GATEWAY response instead. 227 */ 228 if (requestHeaders.isOnlyIfCached() && responseSource.requiresConnection()) { 229 if (responseSource == ResponseSource.CONDITIONAL_CACHE) { 230 IoUtils.closeQuietly(cachedResponseBody); 231 } 232 this.responseSource = ResponseSource.CACHE; 233 this.cacheResponse = BAD_GATEWAY_RESPONSE; 234 RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(cacheResponse.getHeaders()); 235 setResponse(new ResponseHeaders(uri, rawResponseHeaders), cacheResponse.getBody()); 236 } 237 238 if (responseSource.requiresConnection()) { 239 sendSocketRequest(); 240 } else if (connection != null) { 241 HttpConnectionPool.INSTANCE.recycle(connection); 242 connection = null; 243 } 244 } 245 246 /** 247 * Initialize the source for this response. It may be corrected later if the 248 * request headers forbids network use. 249 */ 250 private void initResponseSource() throws IOException { 251 responseSource = ResponseSource.NETWORK; 252 if (!policy.getUseCaches() || responseCache == null) { 253 return; 254 } 255 256 CacheResponse candidate = responseCache.get(uri, method, 257 requestHeaders.getHeaders().toMultimap()); 258 if (candidate == null) { 259 return; 260 } 261 262 Map<String, List<String>> responseHeadersMap = candidate.getHeaders(); 263 cachedResponseBody = candidate.getBody(); 264 if (!acceptCacheResponseType(candidate) 265 || responseHeadersMap == null 266 || cachedResponseBody == null) { 267 IoUtils.closeQuietly(cachedResponseBody); 268 return; 269 } 270 271 RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(responseHeadersMap); 272 cachedResponseHeaders = new ResponseHeaders(uri, rawResponseHeaders); 273 long now = System.currentTimeMillis(); 274 this.responseSource = cachedResponseHeaders.chooseResponseSource(now, requestHeaders); 275 if (responseSource == ResponseSource.CACHE) { 276 this.cacheResponse = candidate; 277 setResponse(cachedResponseHeaders, cachedResponseBody); 278 } else if (responseSource == ResponseSource.CONDITIONAL_CACHE) { 279 this.cacheResponse = candidate; 280 } else if (responseSource == ResponseSource.NETWORK) { 281 IoUtils.closeQuietly(cachedResponseBody); 282 } else { 283 throw new AssertionError(); 284 } 285 } 286 287 private void sendSocketRequest() throws IOException { 288 if (connection == null) { 289 connect(); 290 } 291 292 if (socketOut != null || requestOut != null || socketIn != null) { 293 throw new IllegalStateException(); 294 } 295 296 socketOut = connection.getOutputStream(); 297 requestOut = socketOut; 298 socketIn = connection.getInputStream(); 299 300 if (hasRequestBody()) { 301 initRequestBodyOut(); 302 } 303 } 304 305 /** 306 * Connect to the origin server either directly or via a proxy. 307 */ 308 protected void connect() throws IOException { 309 if (connection == null) { 310 connection = openSocketConnection(); 311 } 312 } 313 314 protected final HttpConnection openSocketConnection() throws IOException { 315 HttpConnection result = HttpConnection.connect(uri, getSslSocketFactory(), 316 policy.getProxy(), requiresTunnel(), policy.getConnectTimeout()); 317 Proxy proxy = result.getAddress().getProxy(); 318 if (proxy != null) { 319 policy.setProxy(proxy); 320 } 321 result.setSoTimeout(policy.getReadTimeout()); 322 return result; 323 } 324 325 protected void initRequestBodyOut() throws IOException { 326 int chunkLength = policy.getChunkLength(); 327 if (chunkLength > 0 || requestHeaders.isChunked()) { 328 sendChunked = true; 329 if (chunkLength == -1) { 330 chunkLength = DEFAULT_CHUNK_LENGTH; 331 } 332 } 333 334 if (socketOut == null) { 335 throw new IllegalStateException("No socket to write to; was a POST cached?"); 336 } 337 338 if (httpMinorVersion == 0) { 339 sendChunked = false; 340 } 341 342 int fixedContentLength = policy.getFixedContentLength(); 343 if (requestBodyOut != null) { 344 // request body was already initialized by the predecessor HTTP engine 345 } else if (fixedContentLength != -1) { 346 writeRequestHeaders(fixedContentLength); 347 requestBodyOut = new FixedLengthOutputStream(requestOut, fixedContentLength); 348 } else if (sendChunked) { 349 writeRequestHeaders(-1); 350 requestBodyOut = new ChunkedOutputStream(requestOut, chunkLength); 351 } else if (requestHeaders.getContentLength() != -1) { 352 writeRequestHeaders(requestHeaders.getContentLength()); 353 requestBodyOut = new RetryableOutputStream(requestHeaders.getContentLength()); 354 } else { 355 requestBodyOut = new RetryableOutputStream(); 356 } 357 } 358 359 /** 360 * @param body the response body, or null if it doesn't exist or isn't 361 * available. 362 */ 363 private void setResponse(ResponseHeaders headers, InputStream body) throws IOException { 364 if (this.responseBodyIn != null) { 365 throw new IllegalStateException(); 366 } 367 this.responseHeaders = headers; 368 this.httpMinorVersion = responseHeaders.getHeaders().getHttpMinorVersion(); 369 if (body != null) { 370 initContentStream(body); 371 } 372 } 373 374 private boolean hasRequestBody() { 375 return method == POST || method == PUT; 376 } 377 378 /** 379 * Returns the request body or null if this request doesn't have a body. 380 */ 381 public final OutputStream getRequestBody() { 382 if (responseSource == null) { 383 throw new IllegalStateException(); 384 } 385 return requestBodyOut; 386 } 387 388 public final boolean hasResponse() { 389 return responseHeaders != null; 390 } 391 392 public final RequestHeaders getRequestHeaders() { 393 return requestHeaders; 394 } 395 396 public final ResponseHeaders getResponseHeaders() { 397 if (responseHeaders == null) { 398 throw new IllegalStateException(); 399 } 400 return responseHeaders; 401 } 402 403 public final int getResponseCode() { 404 if (responseHeaders == null) { 405 throw new IllegalStateException(); 406 } 407 return responseHeaders.getHeaders().getResponseCode(); 408 } 409 410 public final InputStream getResponseBody() { 411 if (responseHeaders == null) { 412 throw new IllegalStateException(); 413 } 414 return responseBodyIn; 415 } 416 417 public final CacheResponse getCacheResponse() { 418 return cacheResponse; 419 } 420 421 public final HttpConnection getConnection() { 422 return connection; 423 } 424 425 public final boolean hasRecycledConnection() { 426 return connection != null && connection.isRecycled(); 427 } 428 429 /** 430 * Returns true if {@code cacheResponse} is of the right type. This 431 * condition is necessary but not sufficient for the cached response to 432 * be used. 433 */ 434 protected boolean acceptCacheResponseType(CacheResponse cacheResponse) { 435 return true; 436 } 437 438 private void maybeCache() throws IOException { 439 // Never cache responses to proxy CONNECT requests. 440 if (method == CONNECT) { 441 return; 442 } 443 444 // Are we caching at all? 445 if (!policy.getUseCaches() || responseCache == null) { 446 return; 447 } 448 449 // Should we cache this response for this request? 450 if (!responseHeaders.isCacheable(requestHeaders)) { 451 return; 452 } 453 454 // Offer this request to the cache. 455 cacheRequest = responseCache.put(uri, getHttpConnectionToCache()); 456 } 457 458 protected HttpURLConnection getHttpConnectionToCache() { 459 return policy; 460 } 461 462 /** 463 * Cause the socket connection to be released to the connection pool when 464 * it is no longer needed. If it is already unneeded, it will be pooled 465 * immediately. 466 */ 467 public final void automaticallyReleaseConnectionToPool() { 468 automaticallyReleaseConnectionToPool = true; 469 if (connection != null && connectionReleased) { 470 HttpConnectionPool.INSTANCE.recycle(connection); 471 connection = null; 472 } 473 } 474 475 /** 476 * Releases this engine so that its resources may be either reused or 477 * closed. 478 */ 479 public final void release(boolean reusable) { 480 // If the response body comes from the cache, close it. 481 if (responseBodyIn == cachedResponseBody) { 482 IoUtils.closeQuietly(responseBodyIn); 483 } 484 485 if (!connectionReleased && connection != null) { 486 connectionReleased = true; 487 488 // We cannot reuse sockets that have incomplete output. 489 if (requestBodyOut != null && !requestBodyOut.closed) { 490 reusable = false; 491 } 492 493 // If the headers specify that the connection shouldn't be reused, don't reuse it. 494 if (hasConnectionCloseHeader()) { 495 reusable = false; 496 } 497 498 if (responseBodyIn instanceof UnknownLengthHttpInputStream) { 499 reusable = false; 500 } 501 502 if (reusable && responseBodyIn != null) { 503 // We must discard the response body before the connection can be reused. 504 try { 505 Streams.skipAll(responseBodyIn); 506 } catch (IOException e) { 507 reusable = false; 508 } 509 } 510 511 if (!reusable) { 512 connection.closeSocketAndStreams(); 513 connection = null; 514 } else if (automaticallyReleaseConnectionToPool) { 515 HttpConnectionPool.INSTANCE.recycle(connection); 516 connection = null; 517 } 518 } 519 } 520 521 private void initContentStream(InputStream transferStream) throws IOException { 522 if (transparentGzip && responseHeaders.isContentEncodingGzip()) { 523 /* 524 * If the response was transparently gzipped, remove the gzip header field 525 * so clients don't double decompress. http://b/3009828 526 */ 527 responseHeaders.stripContentEncoding(); 528 responseBodyIn = new GZIPInputStream(transferStream); 529 } else { 530 responseBodyIn = transferStream; 531 } 532 } 533 534 private InputStream getTransferStream() throws IOException { 535 if (!hasResponseBody()) { 536 return new FixedLengthInputStream(socketIn, cacheRequest, this, 0); 537 } 538 539 if (responseHeaders.isChunked()) { 540 return new ChunkedInputStream(socketIn, cacheRequest, this); 541 } 542 543 if (responseHeaders.getContentLength() != -1) { 544 return new FixedLengthInputStream(socketIn, cacheRequest, this, 545 responseHeaders.getContentLength()); 546 } 547 548 /* 549 * Wrap the input stream from the HttpConnection (rather than 550 * just returning "socketIn" directly here), so that we can control 551 * its use after the reference escapes. 552 */ 553 return new UnknownLengthHttpInputStream(socketIn, cacheRequest, this); 554 } 555 556 private void readResponseHeaders() throws IOException { 557 RawHeaders headers; 558 do { 559 headers = new RawHeaders(); 560 headers.setStatusLine(Streams.readAsciiLine(socketIn)); 561 readHeaders(headers); 562 } while (headers.getResponseCode() == HTTP_CONTINUE); 563 setResponse(new ResponseHeaders(uri, headers), null); 564 } 565 566 /** 567 * Returns true if the response must have a (possibly 0-length) body. 568 * See RFC 2616 section 4.3. 569 */ 570 public final boolean hasResponseBody() { 571 int responseCode = responseHeaders.getHeaders().getResponseCode(); 572 573 // HEAD requests never yield a body regardless of the response headers. 574 if (method == HEAD) { 575 return false; 576 } 577 578 if (method != CONNECT 579 && (responseCode < HTTP_CONTINUE || responseCode >= 200) 580 && responseCode != HttpURLConnectionImpl.HTTP_NO_CONTENT 581 && responseCode != HttpURLConnectionImpl.HTTP_NOT_MODIFIED) { 582 return true; 583 } 584 585 /* 586 * If the Content-Length or Transfer-Encoding headers disagree with the 587 * response code, the response is malformed. For best compatibility, we 588 * honor the headers. 589 */ 590 if (responseHeaders.getContentLength() != -1 || responseHeaders.isChunked()) { 591 return true; 592 } 593 594 return false; 595 } 596 597 /** 598 * Trailers are headers included after the last chunk of a response encoded 599 * with chunked encoding. 600 */ 601 final void readTrailers() throws IOException { 602 readHeaders(responseHeaders.getHeaders()); 603 } 604 605 private void readHeaders(RawHeaders headers) throws IOException { 606 // parse the result headers until the first blank line 607 String line; 608 while (!(line = Streams.readAsciiLine(socketIn)).isEmpty()) { 609 headers.addLine(line); 610 } 611 612 CookieHandler cookieHandler = CookieHandler.getDefault(); 613 if (cookieHandler != null) { 614 cookieHandler.put(uri, headers.toMultimap()); 615 } 616 } 617 618 /** 619 * Prepares the HTTP headers and sends them to the server. 620 * 621 * <p>For streaming requests with a body, headers must be prepared 622 * <strong>before</strong> the output stream has been written to. Otherwise 623 * the body would need to be buffered! 624 * 625 * <p>For non-streaming requests with a body, headers must be prepared 626 * <strong>after</strong> the output stream has been written to and closed. 627 * This ensures that the {@code Content-Length} header field receives the 628 * proper value. 629 * 630 * @param contentLength the number of bytes in the request body, or -1 if 631 * the request body length is unknown. 632 */ 633 private void writeRequestHeaders(int contentLength) throws IOException { 634 if (sentRequestMillis != -1) { 635 throw new IllegalStateException(); 636 } 637 638 RawHeaders headersToSend = getNetworkRequestHeaders(); 639 byte[] bytes = headersToSend.toHeaderString().getBytes(Charsets.ISO_8859_1); 640 641 if (contentLength != -1 && bytes.length + contentLength <= MAX_REQUEST_BUFFER_LENGTH) { 642 requestOut = new BufferedOutputStream(socketOut, bytes.length + contentLength); 643 } 644 645 sentRequestMillis = System.currentTimeMillis(); 646 requestOut.write(bytes); 647 } 648 649 /** 650 * Returns the headers to send on a network request. 651 * 652 * <p>This adds the content length and content-type headers, which are 653 * neither needed nor known when querying the response cache. 654 * 655 * <p>It updates the status line, which may need to be fully qualified if 656 * the connection is using a proxy. 657 */ 658 protected RawHeaders getNetworkRequestHeaders() throws IOException { 659 requestHeaders.getHeaders().setStatusLine(getRequestLine()); 660 661 int fixedContentLength = policy.getFixedContentLength(); 662 if (fixedContentLength != -1) { 663 requestHeaders.setContentLength(fixedContentLength); 664 } else if (sendChunked) { 665 requestHeaders.setChunked(); 666 } else if (requestBodyOut instanceof RetryableOutputStream) { 667 int contentLength = ((RetryableOutputStream) requestBodyOut).contentLength(); 668 requestHeaders.setContentLength(contentLength); 669 } 670 671 return requestHeaders.getHeaders(); 672 } 673 674 /** 675 * Populates requestHeaders with defaults and cookies. 676 * 677 * <p>This client doesn't specify a default {@code Accept} header because it 678 * doesn't know what content types the application is interested in. 679 */ 680 private void prepareRawRequestHeaders() throws IOException { 681 requestHeaders.getHeaders().setStatusLine(getRequestLine()); 682 683 if (requestHeaders.getUserAgent() == null) { 684 requestHeaders.setUserAgent(getDefaultUserAgent()); 685 } 686 687 if (requestHeaders.getHost() == null) { 688 requestHeaders.setHost(getOriginAddress(policy.getURL())); 689 } 690 691 if (httpMinorVersion > 0 && requestHeaders.getConnection() == null) { 692 requestHeaders.setConnection("Keep-Alive"); 693 } 694 695 if (requestHeaders.getAcceptEncoding() == null) { 696 transparentGzip = true; 697 requestHeaders.setAcceptEncoding("gzip"); 698 } 699 700 if (hasRequestBody() && requestHeaders.getContentType() == null) { 701 requestHeaders.setContentType("application/x-www-form-urlencoded"); 702 } 703 704 long ifModifiedSince = policy.getIfModifiedSince(); 705 if (ifModifiedSince != 0) { 706 requestHeaders.setIfModifiedSince(new Date(ifModifiedSince)); 707 } 708 709 CookieHandler cookieHandler = CookieHandler.getDefault(); 710 if (cookieHandler != null) { 711 requestHeaders.addCookies( 712 cookieHandler.get(uri, requestHeaders.getHeaders().toMultimap())); 713 } 714 } 715 716 private String getRequestLine() { 717 String protocol = (httpMinorVersion == 0) ? "HTTP/1.0" : "HTTP/1.1"; 718 return method + " " + requestString() + " " + protocol; 719 } 720 721 private String requestString() { 722 URL url = policy.getURL(); 723 if (includeAuthorityInRequestLine()) { 724 return url.toString(); 725 } else { 726 String fileOnly = url.getFile(); 727 if (fileOnly == null) { 728 fileOnly = "/"; 729 } else if (!fileOnly.startsWith("/")) { 730 fileOnly = "/" + fileOnly; 731 } 732 return fileOnly; 733 } 734 } 735 736 /** 737 * Returns true if the request line should contain the full URL with host 738 * and port (like "GET http://android.com/foo HTTP/1.1") or only the path 739 * (like "GET /foo HTTP/1.1"). 740 * 741 * <p>This is non-final because for HTTPS it's never necessary to supply the 742 * full URL, even if a proxy is in use. 743 */ 744 protected boolean includeAuthorityInRequestLine() { 745 return policy.usingProxy(); 746 } 747 748 /** 749 * Returns the SSL configuration for connections created by this engine. 750 * We cannot reuse HTTPS connections if the socket factory has changed. 751 */ 752 protected SSLSocketFactory getSslSocketFactory() { 753 return null; 754 } 755 756 protected final String getDefaultUserAgent() { 757 String agent = System.getProperty("http.agent"); 758 return agent != null ? agent : ("Java" + System.getProperty("java.version")); 759 } 760 761 private boolean hasConnectionCloseHeader() { 762 return (responseHeaders != null && responseHeaders.hasConnectionClose()) 763 || requestHeaders.hasConnectionClose(); 764 } 765 766 protected final String getOriginAddress(URL url) { 767 int port = url.getPort(); 768 String result = url.getHost(); 769 if (port > 0 && port != policy.getDefaultPort()) { 770 result = result + ":" + port; 771 } 772 return result; 773 } 774 775 protected boolean requiresTunnel() { 776 return false; 777 } 778 779 /** 780 * Flushes the remaining request header and body, parses the HTTP response 781 * headers and starts reading the HTTP response body if it exists. 782 */ 783 public final void readResponse() throws IOException { 784 if (hasResponse()) { 785 return; 786 } 787 788 if (responseSource == null) { 789 throw new IllegalStateException("readResponse() without sendRequest()"); 790 } 791 792 if (!responseSource.requiresConnection()) { 793 return; 794 } 795 796 if (sentRequestMillis == -1) { 797 int contentLength = requestBodyOut instanceof RetryableOutputStream 798 ? ((RetryableOutputStream) requestBodyOut).contentLength() 799 : -1; 800 writeRequestHeaders(contentLength); 801 } 802 803 if (requestBodyOut != null) { 804 requestBodyOut.close(); 805 if (requestBodyOut instanceof RetryableOutputStream) { 806 ((RetryableOutputStream) requestBodyOut).writeToSocket(requestOut); 807 } 808 } 809 810 requestOut.flush(); 811 requestOut = socketOut; 812 813 readResponseHeaders(); 814 responseHeaders.setLocalTimestamps(sentRequestMillis, System.currentTimeMillis()); 815 816 if (responseSource == ResponseSource.CONDITIONAL_CACHE) { 817 if (cachedResponseHeaders.validate(responseHeaders)) { 818 release(true); 819 ResponseHeaders combinedHeaders = cachedResponseHeaders.combine(responseHeaders); 820 setResponse(combinedHeaders, cachedResponseBody); 821 if (responseCache instanceof ExtendedResponseCache) { 822 ExtendedResponseCache httpResponseCache = (ExtendedResponseCache) responseCache; 823 httpResponseCache.trackConditionalCacheHit(); 824 httpResponseCache.update(cacheResponse, getHttpConnectionToCache()); 825 } 826 return; 827 } else { 828 IoUtils.closeQuietly(cachedResponseBody); 829 } 830 } 831 832 if (hasResponseBody()) { 833 maybeCache(); // reentrant. this calls into user code which may call back into this! 834 } 835 836 initContentStream(getTransferStream()); 837 } 838 } 839