• 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.Network;
24 import com.android.volley.NetworkError;
25 import com.android.volley.NetworkResponse;
26 import com.android.volley.NoConnectionError;
27 import com.android.volley.Request;
28 import com.android.volley.RetryPolicy;
29 import com.android.volley.ServerError;
30 import com.android.volley.TimeoutError;
31 import com.android.volley.VolleyError;
32 import com.android.volley.VolleyLog;
33 
34 import org.apache.http.Header;
35 import org.apache.http.HttpEntity;
36 import org.apache.http.HttpResponse;
37 import org.apache.http.HttpStatus;
38 import org.apache.http.StatusLine;
39 import org.apache.http.conn.ConnectTimeoutException;
40 import org.apache.http.impl.cookie.DateUtils;
41 
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.net.MalformedURLException;
45 import java.net.SocketTimeoutException;
46 import java.util.Date;
47 import java.util.HashMap;
48 import java.util.Map;
49 
50 /**
51  * A network performing Volley requests over an {@link HttpStack}.
52  */
53 public class BasicNetwork implements Network {
54     protected static final boolean DEBUG = VolleyLog.DEBUG;
55 
56     private static int SLOW_REQUEST_THRESHOLD_MS = 3000;
57 
58     private static int DEFAULT_POOL_SIZE = 4096;
59 
60     protected final HttpStack mHttpStack;
61 
62     protected final ByteArrayPool mPool;
63 
64     /**
65      * @param httpStack HTTP stack to be used
66      */
BasicNetwork(HttpStack httpStack)67     public BasicNetwork(HttpStack httpStack) {
68         // If a pool isn't passed in, then build a small default pool that will give us a lot of
69         // benefit and not use too much memory.
70         this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
71     }
72 
73     /**
74      * @param httpStack HTTP stack to be used
75      * @param pool a buffer pool that improves GC performance in copy operations
76      */
BasicNetwork(HttpStack httpStack, ByteArrayPool pool)77     public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
78         mHttpStack = httpStack;
79         mPool = pool;
80     }
81 
82     @Override
performRequest(Request<?> request)83     public NetworkResponse performRequest(Request<?> request) throws VolleyError {
84         long requestStart = SystemClock.elapsedRealtime();
85         while (true) {
86             HttpResponse httpResponse = null;
87             byte[] responseContents = null;
88             Map<String, String> responseHeaders = new HashMap<String, String>();
89             try {
90                 // Gather headers.
91                 Map<String, String> headers = new HashMap<String, String>();
92                 addCacheHeaders(headers, request.getCacheEntry());
93                 httpResponse = mHttpStack.performRequest(request, headers);
94                 StatusLine statusLine = httpResponse.getStatusLine();
95                 int statusCode = statusLine.getStatusCode();
96 
97                 responseHeaders = convertHeaders(httpResponse.getAllHeaders());
98                 // Handle cache validation.
99                 if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
100                     return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
101                             request.getCacheEntry().data, responseHeaders, true);
102                 }
103 
104                 responseContents = entityToBytes(httpResponse.getEntity());
105                 // if the request is slow, log it.
106                 long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
107                 logSlowRequests(requestLifetime, request, responseContents, statusLine);
108 
109                 if (statusCode != HttpStatus.SC_OK && statusCode != HttpStatus.SC_NO_CONTENT) {
110                     throw new IOException();
111                 }
112                 return new NetworkResponse(statusCode, responseContents, responseHeaders, false);
113             } catch (SocketTimeoutException e) {
114                 attemptRetryOnException("socket", request, new TimeoutError());
115             } catch (ConnectTimeoutException e) {
116                 attemptRetryOnException("connection", request, new TimeoutError());
117             } catch (MalformedURLException e) {
118                 throw new RuntimeException("Bad URL " + request.getUrl(), e);
119             } catch (IOException e) {
120                 int statusCode = 0;
121                 NetworkResponse networkResponse = null;
122                 if (httpResponse != null) {
123                     statusCode = httpResponse.getStatusLine().getStatusCode();
124                 } else {
125                     throw new NoConnectionError(e);
126                 }
127                 VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
128                 if (responseContents != null) {
129                     networkResponse = new NetworkResponse(statusCode, responseContents,
130                             responseHeaders, false);
131                     if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
132                             statusCode == HttpStatus.SC_FORBIDDEN) {
133                         attemptRetryOnException("auth",
134                                 request, new AuthFailureError(networkResponse));
135                     } else {
136                         // TODO: Only throw ServerError for 5xx status codes.
137                         throw new ServerError(networkResponse);
138                     }
139                 } else {
140                     throw new NetworkError(networkResponse);
141                 }
142             }
143         }
144     }
145 
146     /**
147      * Logs requests that took over SLOW_REQUEST_THRESHOLD_MS to complete.
148      */
logSlowRequests(long requestLifetime, Request<?> request, byte[] responseContents, StatusLine statusLine)149     private void logSlowRequests(long requestLifetime, Request<?> request,
150             byte[] responseContents, StatusLine statusLine) {
151         if (DEBUG || requestLifetime > SLOW_REQUEST_THRESHOLD_MS) {
152             VolleyLog.d("HTTP response for request=<%s> [lifetime=%d], [size=%s], " +
153                     "[rc=%d], [retryCount=%s]", request, requestLifetime,
154                     responseContents != null ? responseContents.length : "null",
155                     statusLine.getStatusCode(), request.getRetryPolicy().getCurrentRetryCount());
156         }
157     }
158 
159     /**
160      * Attempts to prepare the request for a retry. If there are no more attempts remaining in the
161      * request's retry policy, a timeout exception is thrown.
162      * @param request The request to use.
163      */
attemptRetryOnException(String logPrefix, Request<?> request, VolleyError exception)164     private static void attemptRetryOnException(String logPrefix, Request<?> request,
165             VolleyError exception) throws VolleyError {
166         RetryPolicy retryPolicy = request.getRetryPolicy();
167         int oldTimeout = request.getTimeoutMs();
168 
169         try {
170             retryPolicy.retry(exception);
171         } catch (VolleyError e) {
172             request.addMarker(
173                     String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
174             throw e;
175         }
176         request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
177     }
178 
addCacheHeaders(Map<String, String> headers, Cache.Entry entry)179     private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) {
180         // If there's no cache entry, we're done.
181         if (entry == null) {
182             return;
183         }
184 
185         if (entry.etag != null) {
186             headers.put("If-None-Match", entry.etag);
187         }
188 
189         if (entry.serverDate > 0) {
190             Date refTime = new Date(entry.serverDate);
191             headers.put("If-Modified-Since", DateUtils.formatDate(refTime));
192         }
193     }
194 
logError(String what, String url, long start)195     protected void logError(String what, String url, long start) {
196         long now = SystemClock.elapsedRealtime();
197         VolleyLog.v("HTTP ERROR(%s) %d ms to fetch %s", what, (now - start), url);
198     }
199 
200     /** Reads the contents of HttpEntity into a byte[]. */
entityToBytes(HttpEntity entity)201     private byte[] entityToBytes(HttpEntity entity) throws IOException, ServerError {
202         PoolingByteArrayOutputStream bytes =
203                 new PoolingByteArrayOutputStream(mPool, (int) entity.getContentLength());
204         byte[] buffer = null;
205         try {
206             InputStream in = entity.getContent();
207             if (in == null) {
208                 throw new ServerError();
209             }
210             buffer = mPool.getBuf(1024);
211             int count;
212             while ((count = in.read(buffer)) != -1) {
213                 bytes.write(buffer, 0, count);
214             }
215             return bytes.toByteArray();
216         } finally {
217             try {
218                 // Close the InputStream and release the resources by "consuming the content".
219                 entity.consumeContent();
220             } catch (IOException e) {
221                 // This can happen if there was an exception above that left the entity in
222                 // an invalid state.
223                 VolleyLog.v("Error occured when calling consumingContent");
224             }
225             mPool.returnBuf(buffer);
226             bytes.close();
227         }
228     }
229 
230     /**
231      * Converts Headers[] to Map<String, String>.
232      */
convertHeaders(Header[] headers)233     private static Map<String, String> convertHeaders(Header[] headers) {
234         Map<String, String> result = new HashMap<String, String>();
235         for (int i = 0; i < headers.length; i++) {
236             result.put(headers[i].getName(), headers[i].getValue());
237         }
238         return result;
239     }
240 }
241