| 21478 |
rajender |
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 |
*/
|
|
|
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 |
*/
|
|
|
80 |
public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
|
|
|
81 |
mHttpStack = httpStack;
|
|
|
82 |
mPool = pool;
|
|
|
83 |
}
|
|
|
84 |
|
|
|
85 |
@Override
|
|
|
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 |
*/
|
|
|
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 |
*/
|
|
|
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 |
|
|
|
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 |
|
|
|
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[]. */
|
|
|
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 |
*/
|
|
|
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 |
}
|