Blame | Last modification | View Log | RSS feed
/** Copyright (C) 2011 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.android.volley.toolbox;import android.os.SystemClock;import com.android.volley.AuthFailureError;import com.android.volley.Cache;import com.android.volley.Cache.Entry;import com.android.volley.Network;import com.android.volley.NetworkError;import com.android.volley.NetworkResponse;import com.android.volley.NoConnectionError;import com.android.volley.Request;import com.android.volley.RetryPolicy;import com.android.volley.ServerError;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.MalformedURLException;import java.net.SocketTimeoutException;import java.util.Collections;import java.util.Date;import java.util.HashMap;import java.util.Map;import java.util.TreeMap;/*** A network performing Volley requests over an {@link HttpStack}.*/public class BasicNetwork implements Network {protected static final boolean DEBUG = VolleyLog.DEBUG;private static int SLOW_REQUEST_THRESHOLD_MS = 3000;private static int DEFAULT_POOL_SIZE = 4096;protected final HttpStack mHttpStack;protected final ByteArrayPool mPool;/*** @param httpStack HTTP stack to be used*/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.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(HttpStack httpStack, ByteArrayPool pool) {mHttpStack = httpStack;mPool = pool;}@Overridepublic NetworkResponse performRequest(Request<?> request) throws VolleyError {long requestStart = SystemClock.elapsedRealtime();while (true) {HttpResponse httpResponse = null;byte[] responseContents = null;Map<String, String> responseHeaders = Collections.emptyMap();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();responseHeaders = convertHeaders(httpResponse.getAllHeaders());// Handle cache validation.if (statusCode == HttpStatus.SC_NOT_MODIFIED) {Entry entry = request.getCacheEntry();if (entry == null) {return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,responseHeaders, true,SystemClock.elapsedRealtime() - requestStart);}// 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.5entry.responseHeaders.putAll(responseHeaders);return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,entry.responseHeaders, true,SystemClock.elapsedRealtime() - requestStart);}// Some responses such as 204s do not have content. We must check.if (httpResponse.getEntity() != null) {responseContents = entityToBytes(httpResponse.getEntity());} else {// Add 0 byte response as a way of honestly representing a// no-content request.responseContents = new byte[0];}// if the request is slow, log it.long requestLifetime = SystemClock.elapsedRealtime() - requestStart;logSlowRequests(requestLifetime, request, responseContents, statusLine);if (statusCode < 200 || statusCode > 299) {throw new IOException();}return new NetworkResponse(statusCode, responseContents, responseHeaders, false,SystemClock.elapsedRealtime() - requestStart);} 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 = 0;NetworkResponse networkResponse = null;if (httpResponse != null) {statusCode = httpResponse.getStatusLine().getStatusCode();} else {throw new NoConnectionError(e);}VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());if (responseContents != null) {networkResponse = new NetworkResponse(statusCode, responseContents,responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);if (statusCode == HttpStatus.SC_UNAUTHORIZED ||statusCode == HttpStatus.SC_FORBIDDEN) {attemptRetryOnException("auth",request, new AuthFailureError(networkResponse));} else {// TODO: Only throw ServerError for 5xx status codes.throw new ServerError(networkResponse);}} else {throw new NetworkError(networkResponse);}}}}/*** Logs requests that took over SLOW_REQUEST_THRESHOLD_MS to complete.*/private void logSlowRequests(long requestLifetime, Request<?> request,byte[] responseContents, StatusLine statusLine) {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());}}/*** Attempts to prepare the request for a retry. If there are no more attempts remaining in the* request's retry policy, a timeout exception is thrown.* @param request The request to use.*/private static void attemptRetryOnException(String logPrefix, Request<?> request,VolleyError exception) throws VolleyError {RetryPolicy retryPolicy = request.getRetryPolicy();int oldTimeout = request.getTimeoutMs();try {retryPolicy.retry(exception);} catch (VolleyError e) {request.addMarker(String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));throw e;}request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));}private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) {// If there's no cache entry, we're done.if (entry == null) {return;}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));}}protected void logError(String what, String url, long start) {long now = SystemClock.elapsedRealtime();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 {PoolingByteArrayOutputStream bytes =new PoolingByteArrayOutputStream(mPool, (int) entity.getContentLength());byte[] buffer = null;try {InputStream in = entity.getContent();if (in == null) {throw new ServerError();}buffer = mPool.getBuf(1024);int count;while ((count = in.read(buffer)) != -1) {bytes.write(buffer, 0, count);}return bytes.toByteArray();} finally {try {// Close the InputStream and release the resources by "consuming the content".entity.consumeContent();} catch (IOException e) {// This can happen if there was an exception above that left the entity in// an invalid state.VolleyLog.v("Error occured when calling consumingContent");}mPool.returnBuf(buffer);bytes.close();}}/*** Converts Headers[] to Map<String, String>.*/protected static Map<String, String> convertHeaders(Header[] headers) {Map<String, String> result = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);for (int i = 0; i < headers.length; i++) {result.put(headers[i].getName(), headers[i].getValue());}return result;}}