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