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