diff options
Diffstat (limited to 'src/main/java/com/android/volley/toolbox/BasicNetwork.java')
-rw-r--r-- | src/main/java/com/android/volley/toolbox/BasicNetwork.java | 194 |
1 files changed, 138 insertions, 56 deletions
diff --git a/src/main/java/com/android/volley/toolbox/BasicNetwork.java b/src/main/java/com/android/volley/toolbox/BasicNetwork.java index 96fb66e..5330733 100644 --- a/src/main/java/com/android/volley/toolbox/BasicNetwork.java +++ b/src/main/java/com/android/volley/toolbox/BasicNetwork.java @@ -22,6 +22,7 @@ import com.android.volley.AuthFailureError; import com.android.volley.Cache; import com.android.volley.Cache.Entry; import com.android.volley.ClientError; +import com.android.volley.Header; import com.android.volley.Network; import com.android.volley.NetworkError; import com.android.volley.NetworkResponse; @@ -33,23 +34,19 @@ import com.android.volley.TimeoutError; import com.android.volley.VolleyError; import com.android.volley.VolleyLog; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.StatusLine; -import org.apache.http.conn.ConnectTimeoutException; -import org.apache.http.impl.cookie.DateUtils; - import java.io.IOException; import java.io.InputStream; +import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.SocketTimeoutException; +import java.util.ArrayList; import java.util.Collections; -import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Set; import java.util.TreeMap; +import java.util.TreeSet; /** * A network performing Volley requests over an {@link HttpStack}. @@ -61,13 +58,23 @@ public class BasicNetwork implements Network { private static final int DEFAULT_POOL_SIZE = 4096; + /** + * @deprecated Should never have been exposed in the API. This field may be removed in a future + * release of Volley. + */ + @Deprecated protected final HttpStack mHttpStack; + private final BaseHttpStack mBaseHttpStack; + protected final ByteArrayPool mPool; /** * @param httpStack HTTP stack to be used + * @deprecated use {@link #BasicNetwork(BaseHttpStack)} instead to avoid depending on Apache + * HTTP. This method may be removed in a future release of Volley. */ + @Deprecated public BasicNetwork(HttpStack httpStack) { // If a pool isn't passed in, then build a small default pool that will give us a lot of // benefit and not use too much memory. @@ -77,9 +84,36 @@ public class BasicNetwork implements Network { /** * @param httpStack HTTP stack to be used * @param pool a buffer pool that improves GC performance in copy operations + * @deprecated use {@link #BasicNetwork(BaseHttpStack, ByteArrayPool)} instead to avoid + * depending on Apache HTTP. This method may be removed in a future release of + * Volley. */ + @Deprecated public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) { mHttpStack = httpStack; + mBaseHttpStack = new AdaptedHttpStack(httpStack); + mPool = pool; + } + + /** + * @param httpStack HTTP stack to be used + */ + public BasicNetwork(BaseHttpStack httpStack) { + // If a pool isn't passed in, then build a small default pool that will give us a lot of + // benefit and not use too much memory. + this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE)); + } + + /** + * @param httpStack HTTP stack to be used + * @param pool a buffer pool that improves GC performance in copy operations + */ + public BasicNetwork(BaseHttpStack httpStack, ByteArrayPool pool) { + mBaseHttpStack = httpStack; + // Populate mHttpStack for backwards compatibility, since it is a protected field. However, + // we won't use it directly here, so clients which don't access it directly won't need to + // depend on Apache HTTP. + mHttpStack = httpStack; mPool = pool; } @@ -89,39 +123,33 @@ public class BasicNetwork implements Network { while (true) { HttpResponse httpResponse = null; byte[] responseContents = null; - Map<String, String> responseHeaders = Collections.emptyMap(); + List<Header> responseHeaders = Collections.emptyList(); try { // Gather headers. - Map<String, String> headers = new HashMap<String, String>(); - addCacheHeaders(headers, request.getCacheEntry()); - httpResponse = mHttpStack.performRequest(request, headers); - StatusLine statusLine = httpResponse.getStatusLine(); - int statusCode = statusLine.getStatusCode(); + Map<String, String> additionalRequestHeaders = + getCacheHeaders(request.getCacheEntry()); + httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders); + int statusCode = httpResponse.getStatusCode(); - responseHeaders = convertHeaders(httpResponse.getAllHeaders()); + responseHeaders = httpResponse.getHeaders(); // Handle cache validation. - if (statusCode == HttpStatus.SC_NOT_MODIFIED) { - + if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) { Entry entry = request.getCacheEntry(); if (entry == null) { - return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null, - responseHeaders, true, - SystemClock.elapsedRealtime() - requestStart); + return new NetworkResponse(HttpURLConnection.HTTP_NOT_MODIFIED, null, true, + SystemClock.elapsedRealtime() - requestStart, responseHeaders); } - - // A HTTP 304 response does not have all header fields. We - // have to use the header fields from the cache entry plus - // the new ones from the response. - // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5 - entry.responseHeaders.putAll(responseHeaders); - return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data, - entry.responseHeaders, true, - SystemClock.elapsedRealtime() - requestStart); + // Combine cached and response headers so the response will be complete. + List<Header> combinedHeaders = combineHeaders(responseHeaders, entry); + return new NetworkResponse(HttpURLConnection.HTTP_NOT_MODIFIED, entry.data, + true, SystemClock.elapsedRealtime() - requestStart, combinedHeaders); } // Some responses such as 204s do not have content. We must check. - if (httpResponse.getEntity() != null) { - responseContents = entityToBytes(httpResponse.getEntity()); + InputStream inputStream = httpResponse.getContent(); + if (inputStream != null) { + responseContents = + inputStreamToBytes(inputStream, httpResponse.getContentLength()); } else { // Add 0 byte response as a way of honestly representing a // no-content request. @@ -130,33 +158,31 @@ public class BasicNetwork implements Network { // if the request is slow, log it. long requestLifetime = SystemClock.elapsedRealtime() - requestStart; - logSlowRequests(requestLifetime, request, responseContents, statusLine); + logSlowRequests(requestLifetime, request, responseContents, statusCode); if (statusCode < 200 || statusCode > 299) { throw new IOException(); } - return new NetworkResponse(statusCode, responseContents, responseHeaders, false, - SystemClock.elapsedRealtime() - requestStart); + return new NetworkResponse(statusCode, responseContents, false, + SystemClock.elapsedRealtime() - requestStart, responseHeaders); } catch (SocketTimeoutException e) { attemptRetryOnException("socket", request, new TimeoutError()); - } catch (ConnectTimeoutException e) { - attemptRetryOnException("connection", request, new TimeoutError()); } catch (MalformedURLException e) { throw new RuntimeException("Bad URL " + request.getUrl(), e); } catch (IOException e) { int statusCode; if (httpResponse != null) { - statusCode = httpResponse.getStatusLine().getStatusCode(); + statusCode = httpResponse.getStatusCode(); } else { throw new NoConnectionError(e); } VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl()); NetworkResponse networkResponse; if (responseContents != null) { - networkResponse = new NetworkResponse(statusCode, responseContents, - responseHeaders, false, SystemClock.elapsedRealtime() - requestStart); - if (statusCode == HttpStatus.SC_UNAUTHORIZED || - statusCode == HttpStatus.SC_FORBIDDEN) { + networkResponse = new NetworkResponse(statusCode, responseContents, false, + SystemClock.elapsedRealtime() - requestStart, responseHeaders); + if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED || + statusCode == HttpURLConnection.HTTP_FORBIDDEN) { attemptRetryOnException("auth", request, new AuthFailureError(networkResponse)); } else if (statusCode >= 400 && statusCode <= 499) { @@ -184,12 +210,12 @@ public class BasicNetwork implements Network { * Logs requests that took over SLOW_REQUEST_THRESHOLD_MS to complete. */ private void logSlowRequests(long requestLifetime, Request<?> request, - byte[] responseContents, StatusLine statusLine) { + byte[] responseContents, int statusCode) { if (DEBUG || requestLifetime > SLOW_REQUEST_THRESHOLD_MS) { VolleyLog.d("HTTP response for request=<%s> [lifetime=%d], [size=%s], " + "[rc=%d], [retryCount=%s]", request, requestLifetime, responseContents != null ? responseContents.length : "null", - statusLine.getStatusCode(), request.getRetryPolicy().getCurrentRetryCount()); + statusCode, request.getRetryPolicy().getCurrentRetryCount()); } } @@ -213,20 +239,24 @@ public class BasicNetwork implements Network { request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout)); } - private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) { + private Map<String, String> getCacheHeaders(Cache.Entry entry) { // If there's no cache entry, we're done. if (entry == null) { - return; + return Collections.emptyMap(); } + Map<String, String> headers = new HashMap<>(); + if (entry.etag != null) { headers.put("If-None-Match", entry.etag); } if (entry.lastModified > 0) { - Date refTime = new Date(entry.lastModified); - headers.put("If-Modified-Since", DateUtils.formatDate(refTime)); + headers.put("If-Modified-Since", + HttpHeaderParser.formatEpochAsRfc1123(entry.lastModified)); } + + return headers; } protected void logError(String what, String url, long start) { @@ -234,13 +264,13 @@ public class BasicNetwork implements Network { VolleyLog.v("HTTP ERROR(%s) %d ms to fetch %s", what, (now - start), url); } - /** Reads the contents of HttpEntity into a byte[]. */ - private byte[] entityToBytes(HttpEntity entity) throws IOException, ServerError { + /** Reads the contents of an InputStream into a byte[]. */ + private byte[] inputStreamToBytes(InputStream in, int contentLength) + throws IOException, ServerError { PoolingByteArrayOutputStream bytes = - new PoolingByteArrayOutputStream(mPool, (int) entity.getContentLength()); + new PoolingByteArrayOutputStream(mPool, contentLength); byte[] buffer = null; try { - InputStream in = entity.getContent(); if (in == null) { throw new ServerError(); } @@ -253,11 +283,13 @@ public class BasicNetwork implements Network { } finally { try { // Close the InputStream and release the resources by "consuming the content". - entity.consumeContent(); + if (in != null) { + in.close(); + } } catch (IOException e) { - // This can happen if there was an exception above that left the entity in + // This can happen if there was an exception above that left the stream in // an invalid state. - VolleyLog.v("Error occurred when calling consumingContent"); + VolleyLog.v("Error occurred when closing InputStream"); } mPool.returnBuf(buffer); bytes.close(); @@ -266,12 +298,62 @@ public class BasicNetwork implements Network { /** * Converts Headers[] to Map<String, String>. + * + * @deprecated Should never have been exposed in the API. This method may be removed in a future + * release of Volley. */ + @Deprecated protected static Map<String, String> convertHeaders(Header[] headers) { - Map<String, String> result = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER); + Map<String, String> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); for (int i = 0; i < headers.length; i++) { result.put(headers[i].getName(), headers[i].getValue()); } return result; } + + /** + * Combine cache headers with network response headers for an HTTP 304 response. + * + * <p>An HTTP 304 response does not have all header fields. We have to use the header fields + * from the cache entry plus the new ones from the response. See also: + * http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5 + * + * @param responseHeaders Headers from the network response. + * @param entry The cached response. + * @return The combined list of headers. + */ + private static List<Header> combineHeaders(List<Header> responseHeaders, Entry entry) { + // First, create a case-insensitive set of header names from the network + // response. + Set<String> headerNamesFromNetworkResponse = + new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + if (!responseHeaders.isEmpty()) { + for (Header header : responseHeaders) { + headerNamesFromNetworkResponse.add(header.getName()); + } + } + + // Second, add headers from the cache entry to the network response as long as + // they didn't appear in the network response, which should take precedence. + List<Header> combinedHeaders = new ArrayList<>(responseHeaders); + if (entry.allResponseHeaders != null) { + if (!entry.allResponseHeaders.isEmpty()) { + for (Header header : entry.allResponseHeaders) { + if (!headerNamesFromNetworkResponse.contains(header.getName())) { + combinedHeaders.add(header); + } + } + } + } else { + // Legacy caches only have entry.responseHeaders. + if (!entry.responseHeaders.isEmpty()) { + for (Map.Entry<String, String> header : entry.responseHeaders.entrySet()) { + if (!headerNamesFromNetworkResponse.contains(header.getKey())) { + combinedHeaders.add(new Header(header.getKey(), header.getValue())); + } + } + } + } + return combinedHeaders; + } } |