aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/android/volley/toolbox/BasicNetwork.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/android/volley/toolbox/BasicNetwork.java')
-rw-r--r--src/main/java/com/android/volley/toolbox/BasicNetwork.java194
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&lt;String, String&gt;.
+ *
+ * @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;
+ }
}