• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.volley.toolbox;
18 
19 import android.os.SystemClock;
20 
21 import com.android.volley.AuthFailureError;
22 import com.android.volley.Cache;
23 import com.android.volley.Cache.Entry;
24 import com.android.volley.Network;
25 import com.android.volley.NetworkError;
26 import com.android.volley.NetworkResponse;
27 import com.android.volley.NoConnectionError;
28 import com.android.volley.Request;
29 import com.android.volley.RetryPolicy;
30 import com.android.volley.ServerError;
31 import com.android.volley.TimeoutError;
32 import com.android.volley.VolleyError;
33 import com.android.volley.VolleyLog;
34 
35 import org.apache.http.Header;
36 import org.apache.http.HttpEntity;
37 import org.apache.http.HttpResponse;
38 import org.apache.http.HttpStatus;
39 import org.apache.http.StatusLine;
40 import org.apache.http.conn.ConnectTimeoutException;
41 import org.apache.http.impl.cookie.DateUtils;
42 
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.net.MalformedURLException;
46 import java.net.SocketTimeoutException;
47 import java.util.Collections;
48 import java.util.Date;
49 import java.util.HashMap;
50 import java.util.Map;
51 import java.util.TreeMap;
52 
53 /**
54  * A network performing Volley requests over an {@link HttpStack}.
55  */
56 public class BasicNetwork implements Network {
57     protected static final boolean DEBUG = VolleyLog.DEBUG;
58 
59     private static int SLOW_REQUEST_THRESHOLD_MS = 3000;
60 
61     private static int DEFAULT_POOL_SIZE = 4096;
62 
63     protected final HttpStack mHttpStack;
64 
65     protected final ByteArrayPool mPool;
66 
67     /**
68      * @param httpStack HTTP stack to be used
69      */
BasicNetwork(HttpStack httpStack)70     public BasicNetwork(HttpStack httpStack) {
71         // If a pool isn't passed in, then build a small default pool that will give us a lot of
72         // benefit and not use too much memory.
73         this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
74     }
75 
76     /**
77      * @param httpStack HTTP stack to be used
78      * @param pool a buffer pool that improves GC performance in copy operations
79      */
BasicNetwork(HttpStack httpStack, ByteArrayPool pool)80     public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
81         mHttpStack = httpStack;
82         mPool = pool;
83     }
84 
85     @Override
performRequest(Request<?> request)86     public NetworkResponse performRequest(Request<?> request) throws VolleyError {
87         long requestStart = SystemClock.elapsedRealtime();
88         while (true) {
89             HttpResponse httpResponse = null;
90             byte[] responseContents = null;
91             Map<String, String> responseHeaders = Collections.emptyMap();
92             try {
93                 // Gather headers.
94                 Map<String, String> headers = new HashMap<String, String>();
95                 addCacheHeaders(headers, request.getCacheEntry());
96                 httpResponse = mHttpStack.performRequest(request, headers);
97                 StatusLine statusLine = httpResponse.getStatusLine();
98                 int statusCode = statusLine.getStatusCode();
99 
100                 responseHeaders = convertHeaders(httpResponse.getAllHeaders());
101                 // Handle cache validation.
102                 if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
103 
104                     Entry entry = request.getCacheEntry();
105                     if (entry == null) {
106                         return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
107                                 responseHeaders, true,
108                                 SystemClock.elapsedRealtime() - requestStart);
109                     }
110 
111                     // A HTTP 304 response does not have all header fields. We
112                     // have to use the header fields from the cache entry plus
113                     // the new ones from the response.
114                     // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
115                     entry.responseHeaders.putAll(responseHeaders);
116                     return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
117                             entry.responseHeaders, true,
118                             SystemClock.elapsedRealtime() - requestStart);
119                 }
120 
121                 // Some responses such as 204s do not have content.  We must check.
122                 if (httpResponse.getEntity() != null) {
123                   responseContents = entityToBytes(httpResponse.getEntity());
124                 } else {
125                   // Add 0 byte response as a way of honestly representing a
126                   // no-content request.
127                   responseContents = new byte[0];
128                 }
129 
130                 // if the request is slow, log it.
131                 long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
132                 logSlowRequests(requestLifetime, request, responseContents, statusLine);
133 
134                 if (statusCode < 200 || statusCode > 299) {
135                     throw new IOException();
136                 }
137                 return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
138                         SystemClock.elapsedRealtime() - requestStart);
139             } catch (SocketTimeoutException e) {
140                 attemptRetryOnException("socket", request, new TimeoutError());
141             } catch (ConnectTimeoutException e) {
142                 attemptRetryOnException("connection", request, new TimeoutError());
143             } catch (MalformedURLException e) {
144                 throw new RuntimeException("Bad URL " + request.getUrl(), e);
145             } catch (IOException e) {
146                 int statusCode = 0;
147                 NetworkResponse networkResponse = null;
148                 if (httpResponse != null) {
149                     statusCode = httpResponse.getStatusLine().getStatusCode();
150                 } else {
151                     throw new NoConnectionError(e);
152                 }
153                 VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
154                 if (responseContents != null) {
155                     networkResponse = new NetworkResponse(statusCode, responseContents,
156                             responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
157                     if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
158                             statusCode == HttpStatus.SC_FORBIDDEN) {
159                         attemptRetryOnException("auth",
160                                 request, new AuthFailureError(networkResponse));
161                     } else {
162                         // TODO: Only throw ServerError for 5xx status codes.
163                         throw new ServerError(networkResponse);
164                     }
165                 } else {
166                     throw new NetworkError(networkResponse);
167                 }
168             }
169         }
170     }
171 
172     /**
173      * Logs requests that took over SLOW_REQUEST_THRESHOLD_MS to complete.
174      */
logSlowRequests(long requestLifetime, Request<?> request, byte[] responseContents, StatusLine statusLine)175     private void logSlowRequests(long requestLifetime, Request<?> request,
176             byte[] responseContents, StatusLine statusLine) {
177         if (DEBUG || requestLifetime > SLOW_REQUEST_THRESHOLD_MS) {
178             VolleyLog.d("HTTP response for request=<%s> [lifetime=%d], [size=%s], " +
179                     "[rc=%d], [retryCount=%s]", request, requestLifetime,
180                     responseContents != null ? responseContents.length : "null",
181                     statusLine.getStatusCode(), request.getRetryPolicy().getCurrentRetryCount());
182         }
183     }
184 
185     /**
186      * Attempts to prepare the request for a retry. If there are no more attempts remaining in the
187      * request's retry policy, a timeout exception is thrown.
188      * @param request The request to use.
189      */
attemptRetryOnException(String logPrefix, Request<?> request, VolleyError exception)190     private static void attemptRetryOnException(String logPrefix, Request<?> request,
191             VolleyError exception) throws VolleyError {
192         RetryPolicy retryPolicy = request.getRetryPolicy();
193         int oldTimeout = request.getTimeoutMs();
194 
195         try {
196             retryPolicy.retry(exception);
197         } catch (VolleyError e) {
198             request.addMarker(
199                     String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
200             throw e;
201         }
202         request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
203     }
204 
addCacheHeaders(Map<String, String> headers, Cache.Entry entry)205     private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) {
206         // If there's no cache entry, we're done.
207         if (entry == null) {
208             return;
209         }
210 
211         if (entry.etag != null) {
212             headers.put("If-None-Match", entry.etag);
213         }
214 
215         if (entry.lastModified > 0) {
216             Date refTime = new Date(entry.lastModified);
217             headers.put("If-Modified-Since", DateUtils.formatDate(refTime));
218         }
219     }
220 
logError(String what, String url, long start)221     protected void logError(String what, String url, long start) {
222         long now = SystemClock.elapsedRealtime();
223         VolleyLog.v("HTTP ERROR(%s) %d ms to fetch %s", what, (now - start), url);
224     }
225 
226     /** Reads the contents of HttpEntity into a byte[]. */
entityToBytes(HttpEntity entity)227     private byte[] entityToBytes(HttpEntity entity) throws IOException, ServerError {
228         PoolingByteArrayOutputStream bytes =
229                 new PoolingByteArrayOutputStream(mPool, (int) entity.getContentLength());
230         byte[] buffer = null;
231         try {
232             InputStream in = entity.getContent();
233             if (in == null) {
234                 throw new ServerError();
235             }
236             buffer = mPool.getBuf(1024);
237             int count;
238             while ((count = in.read(buffer)) != -1) {
239                 bytes.write(buffer, 0, count);
240             }
241             return bytes.toByteArray();
242         } finally {
243             try {
244                 // Close the InputStream and release the resources by "consuming the content".
245                 entity.consumeContent();
246             } catch (IOException e) {
247                 // This can happen if there was an exception above that left the entity in
248                 // an invalid state.
249                 VolleyLog.v("Error occured when calling consumingContent");
250             }
251             mPool.returnBuf(buffer);
252             bytes.close();
253         }
254     }
255 
256     /**
257      * Converts Headers[] to Map<String, String>.
258      */
convertHeaders(Header[] headers)259     protected static Map<String, String> convertHeaders(Header[] headers) {
260         Map<String, String> result = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
261         for (int i = 0; i < headers.length; i++) {
262             result.put(headers[i].getName(), headers[i].getValue());
263         }
264         return result;
265     }
266 }
267