/** * Initialize the source for this response. It may be corrected later if the request headers * forbids network use. */ private void initResponseSource() throws IOException { responseSource = ResponseSource.NETWORK; if (!policy.getUseCaches()) return; OkResponseCache responseCache = client.getOkResponseCache(); if (responseCache == null) return; CacheResponse candidate = responseCache.get(uri, method, requestHeaders.getHeaders().toMultimap(false)); if (candidate == null) return; Map<String, List<String>> responseHeadersMap = candidate.getHeaders(); cachedResponseBody = candidate.getBody(); if (!acceptCacheResponseType(candidate) || responseHeadersMap == null || cachedResponseBody == null) { Util.closeQuietly(cachedResponseBody); return; } RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(responseHeadersMap, true); cachedResponseHeaders = new ResponseHeaders(uri, rawResponseHeaders); long now = System.currentTimeMillis(); this.responseSource = cachedResponseHeaders.chooseResponseSource(now, requestHeaders); if (responseSource == ResponseSource.CACHE) { this.cacheResponse = candidate; setResponse(cachedResponseHeaders, cachedResponseBody); } else if (responseSource == ResponseSource.CONDITIONAL_CACHE) { this.cacheResponse = candidate; } else if (responseSource == ResponseSource.NETWORK) { Util.closeQuietly(cachedResponseBody); } else { throw new AssertionError(); } }
/** * Figures out what the response source will be, and opens a socket to that source if necessary. * Prepares the request headers and gets ready to start writing the request body if it exists. */ public final void sendRequest() throws IOException { if (responseSource != null) { return; } prepareRawRequestHeaders(); initResponseSource(); OkResponseCache responseCache = client.getOkResponseCache(); if (responseCache != null) { responseCache.trackResponse(responseSource); } // The raw response source may require the network, but the request // headers may forbid network use. In that case, dispose of the network // response and use a GATEWAY_TIMEOUT response instead, as specified // by http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.4. if (requestHeaders.isOnlyIfCached() && responseSource.requiresConnection()) { if (responseSource == ResponseSource.CONDITIONAL_CACHE) { Util.closeQuietly(cachedResponseBody); } this.responseSource = ResponseSource.CACHE; this.cacheResponse = GATEWAY_TIMEOUT_RESPONSE; RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(cacheResponse.getHeaders(), true); setResponse(new ResponseHeaders(uri, rawResponseHeaders), cacheResponse.getBody()); } if (responseSource.requiresConnection()) { sendSocketRequest(); } else if (connection != null) { client.getConnectionPool().recycle(connection); connection = null; } }
/** Connect to the origin server either directly or via a proxy. */ protected final void connect() throws IOException { if (connection != null) { return; } if (routeSelector == null) { String uriHost = uri.getHost(); if (uriHost == null) { throw new UnknownHostException(uri.toString()); } SSLSocketFactory sslSocketFactory = null; HostnameVerifier hostnameVerifier = null; if (uri.getScheme().equalsIgnoreCase("https")) { sslSocketFactory = client.getSslSocketFactory(); hostnameVerifier = client.getHostnameVerifier(); } Address address = new Address( uriHost, getEffectivePort(uri), sslSocketFactory, hostnameVerifier, client.getAuthenticator(), client.getProxy(), client.getTransports()); routeSelector = new RouteSelector( address, uri, client.getProxySelector(), client.getConnectionPool(), Dns.DEFAULT, client.getRoutesDatabase()); } connection = routeSelector.next(method); if (!connection.isConnected()) { connection.connect(client.getConnectTimeout(), client.getReadTimeout(), getTunnelConfig()); client.getConnectionPool().maybeShare(connection); client.getRoutesDatabase().connected(connection.getRoute()); } else if (!connection.isSpdy()) { connection.updateReadTimeout(client.getReadTimeout()); } connected(connection); if (connection.getRoute().getProxy() != client.getProxy()) { // Update the request line if the proxy changed; it may need a host name. requestHeaders.getHeaders().setRequestLine(getRequestLine()); } }
/** * Flushes the remaining request header and body, parses the HTTP response headers and starts * reading the HTTP response body if it exists. */ public final void readResponse() throws IOException { if (hasResponse()) { responseHeaders.setResponseSource(responseSource); return; } if (responseSource == null) { throw new IllegalStateException("readResponse() without sendRequest()"); } if (!responseSource.requiresConnection()) { return; } if (sentRequestMillis == -1) { if (requestBodyOut instanceof RetryableOutputStream) { int contentLength = ((RetryableOutputStream) requestBodyOut).contentLength(); requestHeaders.setContentLength(contentLength); } transport.writeRequestHeaders(); } if (requestBodyOut != null) { requestBodyOut.close(); if (requestBodyOut instanceof RetryableOutputStream) { transport.writeRequestBody((RetryableOutputStream) requestBodyOut); } } transport.flushRequest(); responseHeaders = transport.readResponseHeaders(); responseHeaders.setLocalTimestamps(sentRequestMillis, System.currentTimeMillis()); responseHeaders.setResponseSource(responseSource); if (responseSource == ResponseSource.CONDITIONAL_CACHE) { if (cachedResponseHeaders.validate(responseHeaders)) { release(false); ResponseHeaders combinedHeaders = cachedResponseHeaders.combine(responseHeaders); this.responseHeaders = combinedHeaders; // Update the cache after applying the combined headers but before initializing the content // stream, otherwise the Content-Encoding header (if present) will be stripped from the // combined headers and not end up in the cache file if transparent gzip compression is // turned on. OkResponseCache responseCache = client.getOkResponseCache(); responseCache.trackConditionalCacheHit(); responseCache.update(cacheResponse, policy.getHttpConnectionToCache()); initContentStream(cachedResponseBody); return; } else { Util.closeQuietly(cachedResponseBody); } } if (hasResponseBody()) { maybeCache(); // reentrant. this calls into user code which may call back into this! } initContentStream(transport.getTransferStream(cacheRequest)); }
/** * Populates requestHeaders with defaults and cookies. * * <p>This client doesn't specify a default {@code Accept} header because it doesn't know what * content types the application is interested in. */ private void prepareRawRequestHeaders() throws IOException { requestHeaders.getHeaders().setRequestLine(getRequestLine()); if (requestHeaders.getUserAgent() == null) { requestHeaders.setUserAgent(getDefaultUserAgent()); } if (requestHeaders.getHost() == null) { requestHeaders.setHost(getOriginAddress(policy.getURL())); } if ((connection == null || connection.getHttpMinorVersion() != 0) && requestHeaders.getConnection() == null) { requestHeaders.setConnection("Keep-Alive"); } if (requestHeaders.getAcceptEncoding() == null) { transparentGzip = true; requestHeaders.setAcceptEncoding("gzip"); } if (hasRequestBody() && requestHeaders.getContentType() == null) { requestHeaders.setContentType("application/x-www-form-urlencoded"); } long ifModifiedSince = policy.getIfModifiedSince(); if (ifModifiedSince != 0) { requestHeaders.setIfModifiedSince(new Date(ifModifiedSince)); } CookieHandler cookieHandler = client.getCookieHandler(); if (cookieHandler != null) { requestHeaders.addCookies( cookieHandler.get(uri, requestHeaders.getHeaders().toMultimap(false))); } }