Home | History | Annotate | Download | only in http
      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