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