• 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.net.TrafficStats;
20 import android.net.Uri;
21 import android.os.Handler;
22 import android.os.Looper;
23 import android.text.TextUtils;
24 import androidx.annotation.CallSuper;
25 import androidx.annotation.GuardedBy;
26 import androidx.annotation.Nullable;
27 import com.android.volley.VolleyLog.MarkerLog;
28 import java.io.UnsupportedEncodingException;
29 import java.net.URLEncoder;
30 import java.util.Collections;
31 import java.util.Map;
32 
33 /**
34  * Base class for all network requests.
35  *
36  * @param <T> The type of parsed response this request expects.
37  */
38 public abstract class Request<T> implements Comparable<Request<T>> {
39 
40     /** Default encoding for POST or PUT parameters. See {@link #getParamsEncoding()}. */
41     private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";
42 
43     /** Supported request methods. */
44     public interface Method {
45         int DEPRECATED_GET_OR_POST = -1;
46         int GET = 0;
47         int POST = 1;
48         int PUT = 2;
49         int DELETE = 3;
50         int HEAD = 4;
51         int OPTIONS = 5;
52         int TRACE = 6;
53         int PATCH = 7;
54     }
55 
56     /** Callback to notify when the network request returns. */
57     /* package */ interface NetworkRequestCompleteListener {
58 
59         /** Callback when a network response has been received. */
onResponseReceived(Request<?> request, Response<?> response)60         void onResponseReceived(Request<?> request, Response<?> response);
61 
62         /** Callback when request returns from network without valid response. */
onNoUsableResponseReceived(Request<?> request)63         void onNoUsableResponseReceived(Request<?> request);
64     }
65 
66     /** An event log tracing the lifetime of this request; for debugging. */
67     private final MarkerLog mEventLog = MarkerLog.ENABLED ? new MarkerLog() : null;
68 
69     /**
70      * Request method of this request. Currently supports GET, POST, PUT, DELETE, HEAD, OPTIONS,
71      * TRACE, and PATCH.
72      */
73     private final int mMethod;
74 
75     /** URL of this request. */
76     private final String mUrl;
77 
78     /** Default tag for {@link TrafficStats}. */
79     private final int mDefaultTrafficStatsTag;
80 
81     /** Lock to guard state which can be mutated after a request is added to the queue. */
82     private final Object mLock = new Object();
83 
84     /** Listener interface for errors. */
85     @Nullable
86     @GuardedBy("mLock")
87     private Response.ErrorListener mErrorListener;
88 
89     /** Sequence number of this request, used to enforce FIFO ordering. */
90     private Integer mSequence;
91 
92     /** The request queue this request is associated with. */
93     private RequestQueue mRequestQueue;
94 
95     /** Whether or not responses to this request should be cached. */
96     // TODO(#190): Turn this off by default for anything other than GET requests.
97     private boolean mShouldCache = true;
98 
99     /** Whether or not this request has been canceled. */
100     @GuardedBy("mLock")
101     private boolean mCanceled = false;
102 
103     /** Whether or not a response has been delivered for this request yet. */
104     @GuardedBy("mLock")
105     private boolean mResponseDelivered = false;
106 
107     /** Whether the request should be retried in the event of an HTTP 5xx (server) error. */
108     private boolean mShouldRetryServerErrors = false;
109 
110     /** The retry policy for this request. */
111     private RetryPolicy mRetryPolicy;
112 
113     /**
114      * When a request can be retrieved from cache but must be refreshed from the network, the cache
115      * entry will be stored here so that in the event of a "Not Modified" response, we can be sure
116      * it hasn't been evicted from cache.
117      */
118     private Cache.Entry mCacheEntry = null;
119 
120     /** An opaque token tagging this request; used for bulk cancellation. */
121     private Object mTag;
122 
123     /** Listener that will be notified when a response has been delivered. */
124     @GuardedBy("mLock")
125     private NetworkRequestCompleteListener mRequestCompleteListener;
126 
127     /**
128      * Creates a new request with the given URL and error listener. Note that the normal response
129      * listener is not provided here as delivery of responses is provided by subclasses, who have a
130      * better idea of how to deliver an already-parsed response.
131      *
132      * @deprecated Use {@link #Request(int, String, com.android.volley.Response.ErrorListener)}.
133      */
134     @Deprecated
Request(String url, Response.ErrorListener listener)135     public Request(String url, Response.ErrorListener listener) {
136         this(Method.DEPRECATED_GET_OR_POST, url, listener);
137     }
138 
139     /**
140      * Creates a new request with the given method (one of the values from {@link Method}), URL, and
141      * error listener. Note that the normal response listener is not provided here as delivery of
142      * responses is provided by subclasses, who have a better idea of how to deliver an
143      * already-parsed response.
144      */
Request(int method, String url, @Nullable Response.ErrorListener listener)145     public Request(int method, String url, @Nullable Response.ErrorListener listener) {
146         mMethod = method;
147         mUrl = url;
148         mErrorListener = listener;
149         setRetryPolicy(new DefaultRetryPolicy());
150 
151         mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
152     }
153 
154     /** Return the method for this request. Can be one of the values in {@link Method}. */
getMethod()155     public int getMethod() {
156         return mMethod;
157     }
158 
159     /**
160      * Set a tag on this request. Can be used to cancel all requests with this tag by {@link
161      * RequestQueue#cancelAll(Object)}.
162      *
163      * @return This Request object to allow for chaining.
164      */
setTag(Object tag)165     public Request<?> setTag(Object tag) {
166         mTag = tag;
167         return this;
168     }
169 
170     /**
171      * Returns this request's tag.
172      *
173      * @see Request#setTag(Object)
174      */
getTag()175     public Object getTag() {
176         return mTag;
177     }
178 
179     /** @return this request's {@link com.android.volley.Response.ErrorListener}. */
180     @Nullable
getErrorListener()181     public Response.ErrorListener getErrorListener() {
182         synchronized (mLock) {
183             return mErrorListener;
184         }
185     }
186 
187     /** @return A tag for use with {@link TrafficStats#setThreadStatsTag(int)} */
getTrafficStatsTag()188     public int getTrafficStatsTag() {
189         return mDefaultTrafficStatsTag;
190     }
191 
192     /** @return The hashcode of the URL's host component, or 0 if there is none. */
findDefaultTrafficStatsTag(String url)193     private static int findDefaultTrafficStatsTag(String url) {
194         if (!TextUtils.isEmpty(url)) {
195             Uri uri = Uri.parse(url);
196             if (uri != null) {
197                 String host = uri.getHost();
198                 if (host != null) {
199                     return host.hashCode();
200                 }
201             }
202         }
203         return 0;
204     }
205 
206     /**
207      * Sets the retry policy for this request.
208      *
209      * @return This Request object to allow for chaining.
210      */
setRetryPolicy(RetryPolicy retryPolicy)211     public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
212         mRetryPolicy = retryPolicy;
213         return this;
214     }
215 
216     /** Adds an event to this request's event log; for debugging. */
addMarker(String tag)217     public void addMarker(String tag) {
218         if (MarkerLog.ENABLED) {
219             mEventLog.add(tag, Thread.currentThread().getId());
220         }
221     }
222 
223     /**
224      * Notifies the request queue that this request has finished (successfully or with error).
225      *
226      * <p>Also dumps all events from this request's event log; for debugging.
227      */
finish(final String tag)228     void finish(final String tag) {
229         if (mRequestQueue != null) {
230             mRequestQueue.finish(this);
231         }
232         if (MarkerLog.ENABLED) {
233             final long threadId = Thread.currentThread().getId();
234             if (Looper.myLooper() != Looper.getMainLooper()) {
235                 // If we finish marking off of the main thread, we need to
236                 // actually do it on the main thread to ensure correct ordering.
237                 Handler mainThread = new Handler(Looper.getMainLooper());
238                 mainThread.post(
239                         new Runnable() {
240                             @Override
241                             public void run() {
242                                 mEventLog.add(tag, threadId);
243                                 mEventLog.finish(Request.this.toString());
244                             }
245                         });
246                 return;
247             }
248 
249             mEventLog.add(tag, threadId);
250             mEventLog.finish(this.toString());
251         }
252     }
253 
sendEvent(@equestQueue.RequestEvent int event)254     void sendEvent(@RequestQueue.RequestEvent int event) {
255         if (mRequestQueue != null) {
256             mRequestQueue.sendRequestEvent(this, event);
257         }
258     }
259 
260     /**
261      * Associates this request with the given queue. The request queue will be notified when this
262      * request has finished.
263      *
264      * @return This Request object to allow for chaining.
265      */
setRequestQueue(RequestQueue requestQueue)266     public Request<?> setRequestQueue(RequestQueue requestQueue) {
267         mRequestQueue = requestQueue;
268         return this;
269     }
270 
271     /**
272      * Sets the sequence number of this request. Used by {@link RequestQueue}.
273      *
274      * @return This Request object to allow for chaining.
275      */
setSequence(int sequence)276     public final Request<?> setSequence(int sequence) {
277         mSequence = sequence;
278         return this;
279     }
280 
281     /** Returns the sequence number of this request. */
getSequence()282     public final int getSequence() {
283         if (mSequence == null) {
284             throw new IllegalStateException("getSequence called before setSequence");
285         }
286         return mSequence;
287     }
288 
289     /** Returns the URL of this request. */
getUrl()290     public String getUrl() {
291         return mUrl;
292     }
293 
294     /** Returns the cache key for this request. By default, this is the URL. */
getCacheKey()295     public String getCacheKey() {
296         String url = getUrl();
297         // If this is a GET request, just use the URL as the key.
298         // For callers using DEPRECATED_GET_OR_POST, we assume the method is GET, which matches
299         // legacy behavior where all methods had the same cache key. We can't determine which method
300         // will be used because doing so requires calling getPostBody() which is expensive and may
301         // throw AuthFailureError.
302         // TODO(#190): Remove support for non-GET methods.
303         int method = getMethod();
304         if (method == Method.GET || method == Method.DEPRECATED_GET_OR_POST) {
305             return url;
306         }
307         return Integer.toString(method) + '-' + url;
308     }
309 
310     /**
311      * Annotates this request with an entry retrieved for it from cache. Used for cache coherency
312      * support.
313      *
314      * @return This Request object to allow for chaining.
315      */
setCacheEntry(Cache.Entry entry)316     public Request<?> setCacheEntry(Cache.Entry entry) {
317         mCacheEntry = entry;
318         return this;
319     }
320 
321     /** Returns the annotated cache entry, or null if there isn't one. */
getCacheEntry()322     public Cache.Entry getCacheEntry() {
323         return mCacheEntry;
324     }
325 
326     /**
327      * Mark this request as canceled.
328      *
329      * <p>No callback will be delivered as long as either:
330      *
331      * <ul>
332      *   <li>This method is called on the same thread as the {@link ResponseDelivery} is running on.
333      *       By default, this is the main thread.
334      *   <li>The request subclass being used overrides cancel() and ensures that it does not invoke
335      *       the listener in {@link #deliverResponse} after cancel() has been called in a
336      *       thread-safe manner.
337      * </ul>
338      *
339      * <p>There are no guarantees if both of these conditions aren't met.
340      */
341     @CallSuper
cancel()342     public void cancel() {
343         synchronized (mLock) {
344             mCanceled = true;
345             mErrorListener = null;
346         }
347     }
348 
349     /** Returns true if this request has been canceled. */
isCanceled()350     public boolean isCanceled() {
351         synchronized (mLock) {
352             return mCanceled;
353         }
354     }
355 
356     /**
357      * Returns a list of extra HTTP headers to go along with this request. Can throw {@link
358      * AuthFailureError} as authentication may be required to provide these values.
359      *
360      * @throws AuthFailureError In the event of auth failure
361      */
getHeaders()362     public Map<String, String> getHeaders() throws AuthFailureError {
363         return Collections.emptyMap();
364     }
365 
366     /**
367      * Returns a Map of POST parameters to be used for this request, or null if a simple GET should
368      * be used. Can throw {@link AuthFailureError} as authentication may be required to provide
369      * these values.
370      *
371      * <p>Note that only one of getPostParams() and getPostBody() can return a non-null value.
372      *
373      * @throws AuthFailureError In the event of auth failure
374      * @deprecated Use {@link #getParams()} instead.
375      */
376     @Deprecated
getPostParams()377     protected Map<String, String> getPostParams() throws AuthFailureError {
378         return getParams();
379     }
380 
381     /**
382      * Returns which encoding should be used when converting POST parameters returned by {@link
383      * #getPostParams()} into a raw POST body.
384      *
385      * <p>This controls both encodings:
386      *
387      * <ol>
388      *   <li>The string encoding used when converting parameter names and values into bytes prior to
389      *       URL encoding them.
390      *   <li>The string encoding used when converting the URL encoded parameters into a raw byte
391      *       array.
392      * </ol>
393      *
394      * @deprecated Use {@link #getParamsEncoding()} instead.
395      */
396     @Deprecated
getPostParamsEncoding()397     protected String getPostParamsEncoding() {
398         return getParamsEncoding();
399     }
400 
401     /** @deprecated Use {@link #getBodyContentType()} instead. */
402     @Deprecated
getPostBodyContentType()403     public String getPostBodyContentType() {
404         return getBodyContentType();
405     }
406 
407     /**
408      * Returns the raw POST body to be sent.
409      *
410      * @throws AuthFailureError In the event of auth failure
411      * @deprecated Use {@link #getBody()} instead.
412      */
413     @Deprecated
getPostBody()414     public byte[] getPostBody() throws AuthFailureError {
415         // Note: For compatibility with legacy clients of volley, this implementation must remain
416         // here instead of simply calling the getBody() function because this function must
417         // call getPostParams() and getPostParamsEncoding() since legacy clients would have
418         // overridden these two member functions for POST requests.
419         Map<String, String> postParams = getPostParams();
420         if (postParams != null && postParams.size() > 0) {
421             return encodeParameters(postParams, getPostParamsEncoding());
422         }
423         return null;
424     }
425 
426     /**
427      * Returns a Map of parameters to be used for a POST or PUT request. Can throw {@link
428      * AuthFailureError} as authentication may be required to provide these values.
429      *
430      * <p>Note that you can directly override {@link #getBody()} for custom data.
431      *
432      * @throws AuthFailureError in the event of auth failure
433      */
getParams()434     protected Map<String, String> getParams() throws AuthFailureError {
435         return null;
436     }
437 
438     /**
439      * Returns which encoding should be used when converting POST or PUT parameters returned by
440      * {@link #getParams()} into a raw POST or PUT body.
441      *
442      * <p>This controls both encodings:
443      *
444      * <ol>
445      *   <li>The string encoding used when converting parameter names and values into bytes prior to
446      *       URL encoding them.
447      *   <li>The string encoding used when converting the URL encoded parameters into a raw byte
448      *       array.
449      * </ol>
450      */
getParamsEncoding()451     protected String getParamsEncoding() {
452         return DEFAULT_PARAMS_ENCODING;
453     }
454 
455     /** Returns the content type of the POST or PUT body. */
getBodyContentType()456     public String getBodyContentType() {
457         return "application/x-www-form-urlencoded; charset=" + getParamsEncoding();
458     }
459 
460     /**
461      * Returns the raw POST or PUT body to be sent.
462      *
463      * <p>By default, the body consists of the request parameters in
464      * application/x-www-form-urlencoded format. When overriding this method, consider overriding
465      * {@link #getBodyContentType()} as well to match the new body format.
466      *
467      * @throws AuthFailureError in the event of auth failure
468      */
getBody()469     public byte[] getBody() throws AuthFailureError {
470         Map<String, String> params = getParams();
471         if (params != null && params.size() > 0) {
472             return encodeParameters(params, getParamsEncoding());
473         }
474         return null;
475     }
476 
477     /** Converts <code>params</code> into an application/x-www-form-urlencoded encoded string. */
encodeParameters(Map<String, String> params, String paramsEncoding)478     private byte[] encodeParameters(Map<String, String> params, String paramsEncoding) {
479         StringBuilder encodedParams = new StringBuilder();
480         try {
481             for (Map.Entry<String, String> entry : params.entrySet()) {
482                 if (entry.getKey() == null || entry.getValue() == null) {
483                     throw new IllegalArgumentException(
484                             String.format(
485                                     "Request#getParams() or Request#getPostParams() returned a map "
486                                             + "containing a null key or value: (%s, %s). All keys "
487                                             + "and values must be non-null.",
488                                     entry.getKey(), entry.getValue()));
489                 }
490                 encodedParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding));
491                 encodedParams.append('=');
492                 encodedParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding));
493                 encodedParams.append('&');
494             }
495             return encodedParams.toString().getBytes(paramsEncoding);
496         } catch (UnsupportedEncodingException uee) {
497             throw new RuntimeException("Encoding not supported: " + paramsEncoding, uee);
498         }
499     }
500 
501     /**
502      * Set whether or not responses to this request should be cached.
503      *
504      * @return This Request object to allow for chaining.
505      */
setShouldCache(boolean shouldCache)506     public final Request<?> setShouldCache(boolean shouldCache) {
507         mShouldCache = shouldCache;
508         return this;
509     }
510 
511     /** Returns true if responses to this request should be cached. */
shouldCache()512     public final boolean shouldCache() {
513         return mShouldCache;
514     }
515 
516     /**
517      * Sets whether or not the request should be retried in the event of an HTTP 5xx (server) error.
518      *
519      * @return This Request object to allow for chaining.
520      */
setShouldRetryServerErrors(boolean shouldRetryServerErrors)521     public final Request<?> setShouldRetryServerErrors(boolean shouldRetryServerErrors) {
522         mShouldRetryServerErrors = shouldRetryServerErrors;
523         return this;
524     }
525 
526     /**
527      * Returns true if this request should be retried in the event of an HTTP 5xx (server) error.
528      */
shouldRetryServerErrors()529     public final boolean shouldRetryServerErrors() {
530         return mShouldRetryServerErrors;
531     }
532 
533     /**
534      * Priority values. Requests will be processed from higher priorities to lower priorities, in
535      * FIFO order.
536      */
537     public enum Priority {
538         LOW,
539         NORMAL,
540         HIGH,
541         IMMEDIATE
542     }
543 
544     /** Returns the {@link Priority} of this request; {@link Priority#NORMAL} by default. */
getPriority()545     public Priority getPriority() {
546         return Priority.NORMAL;
547     }
548 
549     /**
550      * Returns the socket timeout in milliseconds per retry attempt. (This value can be changed per
551      * retry attempt if a backoff is specified via backoffTimeout()). If there are no retry attempts
552      * remaining, this will cause delivery of a {@link TimeoutError} error.
553      */
getTimeoutMs()554     public final int getTimeoutMs() {
555         return getRetryPolicy().getCurrentTimeout();
556     }
557 
558     /** Returns the retry policy that should be used for this request. */
getRetryPolicy()559     public RetryPolicy getRetryPolicy() {
560         return mRetryPolicy;
561     }
562 
563     /**
564      * Mark this request as having a response delivered on it. This can be used later in the
565      * request's lifetime for suppressing identical responses.
566      */
markDelivered()567     public void markDelivered() {
568         synchronized (mLock) {
569             mResponseDelivered = true;
570         }
571     }
572 
573     /** Returns true if this request has had a response delivered for it. */
hasHadResponseDelivered()574     public boolean hasHadResponseDelivered() {
575         synchronized (mLock) {
576             return mResponseDelivered;
577         }
578     }
579 
580     /**
581      * Subclasses must implement this to parse the raw network response and return an appropriate
582      * response type. This method will be called from a worker thread. The response will not be
583      * delivered if you return null.
584      *
585      * @param response Response from the network
586      * @return The parsed response, or null in the case of an error
587      */
parseNetworkResponse(NetworkResponse response)588     protected abstract Response<T> parseNetworkResponse(NetworkResponse response);
589 
590     /**
591      * Subclasses can override this method to parse 'networkError' and return a more specific error.
592      *
593      * <p>The default implementation just returns the passed 'networkError'.
594      *
595      * @param volleyError the error retrieved from the network
596      * @return an NetworkError augmented with additional information
597      */
parseNetworkError(VolleyError volleyError)598     protected VolleyError parseNetworkError(VolleyError volleyError) {
599         return volleyError;
600     }
601 
602     /**
603      * Subclasses must implement this to perform delivery of the parsed response to their listeners.
604      * The given response is guaranteed to be non-null; responses that fail to parse are not
605      * delivered.
606      *
607      * @param response The parsed response returned by {@link
608      *     #parseNetworkResponse(NetworkResponse)}
609      */
deliverResponse(T response)610     protected abstract void deliverResponse(T response);
611 
612     /**
613      * Delivers error message to the ErrorListener that the Request was initialized with.
614      *
615      * @param error Error details
616      */
deliverError(VolleyError error)617     public void deliverError(VolleyError error) {
618         Response.ErrorListener listener;
619         synchronized (mLock) {
620             listener = mErrorListener;
621         }
622         if (listener != null) {
623             listener.onErrorResponse(error);
624         }
625     }
626 
627     /**
628      * {@link NetworkRequestCompleteListener} that will receive callbacks when the request returns
629      * from the network.
630      */
setNetworkRequestCompleteListener( NetworkRequestCompleteListener requestCompleteListener)631     /* package */ void setNetworkRequestCompleteListener(
632             NetworkRequestCompleteListener requestCompleteListener) {
633         synchronized (mLock) {
634             mRequestCompleteListener = requestCompleteListener;
635         }
636     }
637 
638     /**
639      * Notify NetworkRequestCompleteListener that a valid response has been received which can be
640      * used for other, waiting requests.
641      *
642      * @param response received from the network
643      */
notifyListenerResponseReceived(Response<?> response)644     /* package */ void notifyListenerResponseReceived(Response<?> response) {
645         NetworkRequestCompleteListener listener;
646         synchronized (mLock) {
647             listener = mRequestCompleteListener;
648         }
649         if (listener != null) {
650             listener.onResponseReceived(this, response);
651         }
652     }
653 
654     /**
655      * Notify NetworkRequestCompleteListener that the network request did not result in a response
656      * which can be used for other, waiting requests.
657      */
notifyListenerResponseNotUsable()658     /* package */ void notifyListenerResponseNotUsable() {
659         NetworkRequestCompleteListener listener;
660         synchronized (mLock) {
661             listener = mRequestCompleteListener;
662         }
663         if (listener != null) {
664             listener.onNoUsableResponseReceived(this);
665         }
666     }
667 
668     /**
669      * Our comparator sorts from high to low priority, and secondarily by sequence number to provide
670      * FIFO ordering.
671      */
672     @Override
compareTo(Request<T> other)673     public int compareTo(Request<T> other) {
674         Priority left = this.getPriority();
675         Priority right = other.getPriority();
676 
677         // High-priority requests are "lesser" so they are sorted to the front.
678         // Equal priorities are sorted by sequence number to provide FIFO ordering.
679         return left == right ? this.mSequence - other.mSequence : right.ordinal() - left.ordinal();
680     }
681 
682     @Override
toString()683     public String toString() {
684         String trafficStatsTag = "0x" + Integer.toHexString(getTrafficStatsTag());
685         return (isCanceled() ? "[X] " : "[ ] ")
686                 + getUrl()
687                 + " "
688                 + trafficStatsTag
689                 + " "
690                 + getPriority()
691                 + " "
692                 + mSequence;
693     }
694 }
695