Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
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
}