• 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;
18 
19 import android.os.Handler;
20 import android.os.Looper;
21 import android.os.SystemClock;
22 
23 import com.android.volley.VolleyLog.MarkerLog;
24 
25 import java.io.UnsupportedEncodingException;
26 import java.net.URLEncoder;
27 import java.util.Collections;
28 import java.util.Map;
29 
30 /**
31  * Base class for all network requests.
32  *
33  * @param <T> The type of parsed response this request expects.
34  */
35 public abstract class Request<T> implements Comparable<Request<T>> {
36 
37     /** Default encoding for POST parameters. See {@link #getPostParamsEncoding()}. */
38     private static final String DEFAULT_POST_PARAMS_ENCODING = "UTF-8";
39 
40     /** An event log tracing the lifetime of this request; for debugging. */
41     private final MarkerLog mEventLog = MarkerLog.ENABLED ? new MarkerLog() : null;
42 
43     /** URL of this request. */
44     private final String mUrl;
45 
46     /** Listener interface for errors. */
47     private final Response.ErrorListener mErrorListener;
48 
49     /** Sequence number of this request, used to enforce FIFO ordering. */
50     private Integer mSequence;
51 
52     /** The request queue this request is associated with. */
53     private RequestQueue mRequestQueue;
54 
55     /** Whether or not responses to this request should be cached. */
56     private boolean mShouldCache = true;
57 
58     /** Whether or not this request has been canceled. */
59     private boolean mCanceled = false;
60 
61     /** Whether or not this request should be suppressed by {@link RequestQueue#drain()}. */
62     private boolean mDrainable = true;
63 
64     /** Whether or not a response has been delivered for this request yet. */
65     private boolean mResponseDelivered = false;
66 
67     // A cheap variant of request tracing used to dump slow requests.
68     private long mRequestBirthTime = 0;
69 
70     /** Threshold at which we should log the request (even when debug logging is not enabled). */
71     private static final long SLOW_REQUEST_THRESHOLD_MS = 3000;
72 
73     /** The retry policy for this request. */
74     private RetryPolicy mRetryPolicy;
75 
76     /**
77      * When a request can be retrieved from cache but must be refreshed from
78      * the network, the cache entry will be stored here so that in the event of
79      * a "Not Modified" response, we can be sure it hasn't been evicted from cache.
80      */
81     private Cache.Entry mCacheEntry = null;
82 
83     /** An opaque token tagging this request; used for bulk cancellation. */
84     private Object mTag;
85 
86     /**
87      * Creates a new request with the given URL and error listener.  Note that
88      * the normal response listener is not provided here as delivery of responses
89      * is provided by subclasses, who have a better idea of how to deliver an
90      * already-parsed response.
91      */
Request(String url, Response.ErrorListener listener)92     public Request(String url, Response.ErrorListener listener) {
93         mUrl = url;
94         mErrorListener = listener;
95         setRetryPolicy(new DefaultRetryPolicy());
96     }
97 
98     /**
99      * Set a tag on this request. Can be used to cancel all requests with this
100      * tag by {@link RequestQueue#cancelAll(Object)}.
101      */
setTag(Object tag)102     public void setTag(Object tag) {
103         mTag = tag;
104     }
105 
106     /**
107      * Returns this request's tag.
108      * @see Request#setTag(Object)
109      */
getTag()110     public Object getTag() {
111         return mTag;
112     }
113 
114     /**
115      * Sets the retry policy for this request.
116      */
setRetryPolicy(RetryPolicy retryPolicy)117     public void setRetryPolicy(RetryPolicy retryPolicy) {
118         mRetryPolicy = retryPolicy;
119     }
120 
121     /**
122      * Adds an event to this request's event log; for debugging.
123      */
addMarker(String tag)124     public void addMarker(String tag) {
125         if (MarkerLog.ENABLED) {
126             mEventLog.add(tag, Thread.currentThread().getId());
127         } else if (mRequestBirthTime == 0) {
128             mRequestBirthTime = SystemClock.elapsedRealtime();
129         }
130     }
131 
132     /**
133      * Notifies the request queue that this request has finished (successfully or with error).
134      *
135      * <p>Also dumps all events from this request's event log; for debugging.</p>
136      */
finish(final String tag)137     void finish(final String tag) {
138         if (mRequestQueue != null) {
139             mRequestQueue.finish(this);
140         }
141         if (MarkerLog.ENABLED) {
142             final long threadId = Thread.currentThread().getId();
143             if (Looper.myLooper() != Looper.getMainLooper()) {
144                 // If we finish marking off of the main thread, we need to
145                 // actually do it on the main thread to ensure correct ordering.
146                 Handler mainThread = new Handler(Looper.getMainLooper());
147                 mainThread.post(new Runnable() {
148                     @Override
149                     public void run() {
150                         mEventLog.add(tag, threadId);
151                         mEventLog.finish(this.toString());
152                     }
153                 });
154                 return;
155             }
156 
157             mEventLog.add(tag, threadId);
158             mEventLog.finish(this.toString());
159         } else {
160             long requestTime = SystemClock.elapsedRealtime() - mRequestBirthTime;
161             if (requestTime >= SLOW_REQUEST_THRESHOLD_MS) {
162                 VolleyLog.d("%d ms: %s", requestTime, this.toString());
163             }
164         }
165     }
166 
167     /**
168      * Associates this request with the given queue. The request queue will be notified when this
169      * request has finished.
170      */
setRequestQueue(RequestQueue requestQueue)171     public void setRequestQueue(RequestQueue requestQueue) {
172         mRequestQueue = requestQueue;
173     }
174 
175     /**
176      * Sets the sequence number of this request.  Used by {@link RequestQueue}.
177      */
setSequence(int sequence)178     public final void setSequence(int sequence) {
179         mSequence = sequence;
180     }
181 
182     /**
183      * Returns the sequence number of this request.
184      */
getSequence()185     public final int getSequence() {
186         if (mSequence == null) {
187             throw new IllegalStateException("getSequence called before setSequence");
188         }
189         return mSequence;
190     }
191 
192     /**
193      * Sets whether or not this request should be suppressed by {@link RequestQueue#drain()}.
194      */
195     @Deprecated
setDrainable(boolean drainable)196     public void setDrainable(boolean drainable) {
197         mDrainable = drainable;
198     }
199 
200     /**
201      * Returns true if this request can be suppressed by {@link RequestQueue#drain()}.
202      */
203     @Deprecated
isDrainable()204     public boolean isDrainable() {
205         return mDrainable;
206     }
207 
208     /**
209      * Returns the URL of this request.
210      */
getUrl()211     public String getUrl() {
212         return mUrl;
213     }
214 
215     /**
216      * Returns the cache key for this request.  By default, this is the URL.
217      */
getCacheKey()218     public String getCacheKey() {
219         return getUrl();
220     }
221 
222     /**
223      * Annotates this request with an entry retrieved for it from cache.
224      * Used for cache coherency support.
225      */
setCacheEntry(Cache.Entry entry)226     public void setCacheEntry(Cache.Entry entry) {
227         mCacheEntry = entry;
228     }
229 
230     /**
231      * Returns the annotated cache entry, or null if there isn't one.
232      */
getCacheEntry()233     public Cache.Entry getCacheEntry() {
234         return mCacheEntry;
235     }
236 
237     /**
238      * Mark this request as canceled.  No callback will be delivered.
239      */
cancel()240     public void cancel() {
241         mCanceled = true;
242     }
243 
244     /**
245      * Returns true if this request has been canceled.
246      */
isCanceled()247     public boolean isCanceled() {
248         return mCanceled;
249     }
250 
251     /**
252      * Returns a list of extra HTTP headers to go along with this request. Can
253      * throw {@link AuthFailureError} as authentication may be required to
254      * provide these values.
255      * @throws AuthFailureError In the event of auth failure
256      */
getHeaders()257     public Map<String, String> getHeaders() throws AuthFailureError {
258         return Collections.emptyMap();
259     }
260 
261     /**
262      * Returns a Map of POST parameters to be used for this request, or null if
263      * a simple GET should be used.  Can throw {@link AuthFailureError} as
264      * authentication may be required to provide these values.
265      *
266      * <p>Note that only one of getPostParams() and getPostBody() can return a non-null
267      * value.</p>
268      * @throws AuthFailureError In the event of auth failure
269      */
getPostParams()270     protected Map<String, String> getPostParams() throws AuthFailureError {
271         return null;
272     }
273 
274     /**
275      * Returns which encoding should be used when converting POST parameters returned by
276      * {@link #getPostParams()} into a raw POST body.
277      *
278      * <p>This controls both encodings:
279      * <ol>
280      *     <li>The string encoding used when converting parameter names and values into bytes prior
281      *         to URL encoding them.</li>
282      *     <li>The string encoding used when converting the URL encoded parameters into a raw
283      *         byte array.</li>
284      * </ol>
285      */
getPostParamsEncoding()286     protected String getPostParamsEncoding() {
287         return DEFAULT_POST_PARAMS_ENCODING;
288     }
289 
getPostBodyContentType()290     public String getPostBodyContentType() {
291         return "application/x-www-form-urlencoded; charset=" + getPostParamsEncoding();
292     }
293 
294     /**
295      * Returns the raw POST body to be sent.
296      *
297      * @throws AuthFailureError In the event of auth failure
298      */
getPostBody()299     public byte[] getPostBody() throws AuthFailureError {
300         Map<String, String> postParams = getPostParams();
301         if (postParams != null && postParams.size() > 0) {
302             return encodePostParameters(postParams, getPostParamsEncoding());
303         }
304         return null;
305     }
306 
307     /**
308      * Converts <code>postParams</code> into an application/x-www-form-urlencoded encoded string.
309      */
encodePostParameters(Map<String, String> postParams, String postParamsEncoding)310     private byte[] encodePostParameters(Map<String, String> postParams, String postParamsEncoding) {
311         StringBuilder encodedParams = new StringBuilder();
312         try {
313             for (Map.Entry<String, String> entry : postParams.entrySet()) {
314                 encodedParams.append(URLEncoder.encode(entry.getKey(), postParamsEncoding));
315                 encodedParams.append('=');
316                 encodedParams.append(URLEncoder.encode(entry.getValue(), postParamsEncoding));
317                 encodedParams.append('&');
318             }
319             return encodedParams.toString().getBytes(postParamsEncoding);
320         } catch (UnsupportedEncodingException uee) {
321             throw new RuntimeException("Encoding not supported: " + postParamsEncoding, uee);
322         }
323     }
324 
325     /**
326      * Set whether or not responses to this request should be cached.
327      */
setShouldCache(boolean shouldCache)328     public final void setShouldCache(boolean shouldCache) {
329         mShouldCache = shouldCache;
330     }
331 
332     /**
333      * Returns true if responses to this request should be cached.
334      */
shouldCache()335     public final boolean shouldCache() {
336         return mShouldCache;
337     }
338 
339     /**
340      * Priority values.  Requests will be processed from higher priorities to
341      * lower priorities, in FIFO order.
342      */
343     public enum Priority {
344         LOW,
345         NORMAL,
346         HIGH,
347         IMMEDIATE
348     }
349 
350     /**
351      * Returns the {@link Priority} of this request; {@link Priority#NORMAL} by default.
352      */
getPriority()353     public Priority getPriority() {
354         return Priority.NORMAL;
355     }
356 
357     /**
358      * Returns the socket timeout in milliseconds per retry attempt. (This value can be changed
359      * per retry attempt if a backoff is specified via backoffTimeout()). If there are no retry
360      * attempts remaining, this will cause delivery of a {@link TimeoutError} error.
361      */
getTimeoutMs()362     public final int getTimeoutMs() {
363         return mRetryPolicy.getCurrentTimeout();
364     }
365 
366     /**
367      * Returns the retry policy that should be used  for this request.
368      */
getRetryPolicy()369     public RetryPolicy getRetryPolicy() {
370         return mRetryPolicy;
371     }
372 
373     /**
374      * Mark this request as having a response delivered on it.  This can be used
375      * later in the request's lifetime for suppressing identical responses.
376      */
markDelivered()377     public void markDelivered() {
378         mResponseDelivered = true;
379     }
380 
381     /**
382      * Returns true if this request has had a response delivered for it.
383      */
hasHadResponseDelivered()384     public boolean hasHadResponseDelivered() {
385         return mResponseDelivered;
386     }
387 
388     /**
389      * Subclasses must implement this to parse the raw network response
390      * and return an appropriate response type. This method will be
391      * called from a worker thread.  The response will not be delivered
392      * if you return null.
393      * @param response Response from the network
394      * @return The parsed response, or null in the case of an error
395      */
parseNetworkResponse(NetworkResponse response)396     abstract protected Response<T> parseNetworkResponse(NetworkResponse response);
397 
398     /**
399      * Subclasses can override this method to parse 'networkError' and return a more specific error.
400      *
401      * <p>The default implementation just returns the passed 'networkError'.</p>
402      *
403      * @param volleyError the error retrieved from the network
404      * @return an NetworkError augmented with additional information
405      */
parseNetworkError(VolleyError volleyError)406     protected VolleyError parseNetworkError(VolleyError volleyError) {
407         return volleyError;
408     }
409 
410     /**
411      * Subclasses must implement this to perform delivery of the parsed
412      * response to their listeners.  The given response is guaranteed to
413      * be non-null; responses that fail to parse are not delivered.
414      * @param response The parsed response returned by
415      * {@link #parseNetworkResponse(NetworkResponse)}
416      */
deliverResponse(T response)417     abstract protected void deliverResponse(T response);
418 
419     /**
420      * Delivers error message to the ErrorListener that the Request was
421      * initialized with.
422      *
423      * @param error Error details
424      */
deliverError(VolleyError error)425     public void deliverError(VolleyError error) {
426         if (mErrorListener != null) {
427             mErrorListener.onErrorResponse(error);
428         }
429     }
430 
431     /**
432      * Our comparator sorts from high to low priority, and secondarily by
433      * sequence number to provide FIFO ordering.
434      */
435     @Override
compareTo(Request<T> other)436     public int compareTo(Request<T> other) {
437         Priority left = this.getPriority();
438         Priority right = other.getPriority();
439 
440         // High-priority requests are "lesser" so they are sorted to the front.
441         // Equal priorities are sorted by sequence number to provide FIFO ordering.
442         return left == right ?
443                 this.mSequence - other.mSequence :
444                 right.ordinal() - left.ordinal();
445     }
446 
447     @Override
toString()448     public String toString() {
449         return (mCanceled ? "[X] " : "[ ] ") + getUrl() + " " + getPriority() + " " + mSequence;
450     }
451 }
452