• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
HttpEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders, HttpConnection connection, RetryableOutputStream requestBodyOut)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 
getUri()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      */
sendRequest()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      */
initResponseSource()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 
sendSocketRequest()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      */
connect()308     protected void connect() throws IOException {
309         if (connection == null) {
310             connection = openSocketConnection();
311         }
312     }
313 
openSocketConnection()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 
initRequestBodyOut()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      */
setResponse(ResponseHeaders headers, InputStream body)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 
hasRequestBody()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      */
getRequestBody()381     public final OutputStream getRequestBody() {
382         if (responseSource == null) {
383             throw new IllegalStateException();
384         }
385         return requestBodyOut;
386     }
387 
hasResponse()388     public final boolean hasResponse() {
389         return responseHeaders != null;
390     }
391 
getRequestHeaders()392     public final RequestHeaders getRequestHeaders() {
393         return requestHeaders;
394     }
395 
getResponseHeaders()396     public final ResponseHeaders getResponseHeaders() {
397         if (responseHeaders == null) {
398             throw new IllegalStateException();
399         }
400         return responseHeaders;
401     }
402 
getResponseCode()403     public final int getResponseCode() {
404         if (responseHeaders == null) {
405             throw new IllegalStateException();
406         }
407         return responseHeaders.getHeaders().getResponseCode();
408     }
409 
getResponseBody()410     public final InputStream getResponseBody() {
411         if (responseHeaders == null) {
412             throw new IllegalStateException();
413         }
414         return responseBodyIn;
415     }
416 
getCacheResponse()417     public final CacheResponse getCacheResponse() {
418         return cacheResponse;
419     }
420 
getConnection()421     public final HttpConnection getConnection() {
422         return connection;
423     }
424 
hasRecycledConnection()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      */
acceptCacheResponseType(CacheResponse cacheResponse)434     protected boolean acceptCacheResponseType(CacheResponse cacheResponse) {
435         return true;
436     }
437 
maybeCache()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 
getHttpConnectionToCache()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      */
automaticallyReleaseConnectionToPool()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      */
release(boolean reusable)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 
initContentStream(InputStream transferStream)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 
getTransferStream()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 
readResponseHeaders()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      */
hasResponseBody()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      */
readTrailers()601     final void readTrailers() throws IOException {
602         readHeaders(responseHeaders.getHeaders());
603     }
604 
readHeaders(RawHeaders headers)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      */
writeRequestHeaders(int contentLength)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      */
getNetworkRequestHeaders()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      */
prepareRawRequestHeaders()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 
getRequestLine()716     private String getRequestLine() {
717         String protocol = (httpMinorVersion == 0) ? "HTTP/1.0" : "HTTP/1.1";
718         return method + " " + requestString() + " " + protocol;
719     }
720 
requestString()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      */
includeAuthorityInRequestLine()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      */
getSslSocketFactory()752     protected SSLSocketFactory getSslSocketFactory() {
753         return null;
754     }
755 
getDefaultUserAgent()756     protected final String getDefaultUserAgent() {
757         String agent = System.getProperty("http.agent");
758         return agent != null ? agent : ("Java" + System.getProperty("java.version"));
759     }
760 
hasConnectionCloseHeader()761     private boolean hasConnectionCloseHeader() {
762         return (responseHeaders != null && responseHeaders.hasConnectionClose())
763                 || requestHeaders.hasConnectionClose();
764     }
765 
getOriginAddress(URL url)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 
requiresTunnel()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      */
readResponse()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