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