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