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