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