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
17package com.android.volley;
18
19import android.net.TrafficStats;
20import android.net.Uri;
21import android.os.Handler;
22import android.os.Looper;
23import android.os.SystemClock;
24import android.text.TextUtils;
25
26import com.android.volley.VolleyLog.MarkerLog;
27
28import java.io.UnsupportedEncodingException;
29import java.net.URLEncoder;
30import java.util.Collections;
31import 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 */
38public 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 this request's {@link com.android.volley.Response.ErrorListener}.
168     */
169    public Response.ErrorListener getErrorListener() {
170        return mErrorListener;
171    }
172
173    /**
174     * @return A tag for use with {@link TrafficStats#setThreadStatsTag(int)}
175     */
176    public int getTrafficStatsTag() {
177        return mDefaultTrafficStatsTag;
178    }
179
180    /**
181     * @return The hashcode of the URL's host component, or 0 if there is none.
182     */
183    private static int findDefaultTrafficStatsTag(String url) {
184        if (!TextUtils.isEmpty(url)) {
185            Uri uri = Uri.parse(url);
186            if (uri != null) {
187                String host = uri.getHost();
188                if (host != null) {
189                    return host.hashCode();
190                }
191            }
192        }
193        return 0;
194    }
195
196    /**
197     * Sets the retry policy for this request.
198     *
199     * @return This Request object to allow for chaining.
200     */
201    public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
202        mRetryPolicy = retryPolicy;
203        return this;
204    }
205
206    /**
207     * Adds an event to this request's event log; for debugging.
208     */
209    public void addMarker(String tag) {
210        if (MarkerLog.ENABLED) {
211            mEventLog.add(tag, Thread.currentThread().getId());
212        } else if (mRequestBirthTime == 0) {
213            mRequestBirthTime = SystemClock.elapsedRealtime();
214        }
215    }
216
217    /**
218     * Notifies the request queue that this request has finished (successfully or with error).
219     *
220     * <p>Also dumps all events from this request's event log; for debugging.</p>
221     */
222    void finish(final String tag) {
223        if (mRequestQueue != null) {
224            mRequestQueue.finish(this);
225        }
226        if (MarkerLog.ENABLED) {
227            final long threadId = Thread.currentThread().getId();
228            if (Looper.myLooper() != Looper.getMainLooper()) {
229                // If we finish marking off of the main thread, we need to
230                // actually do it on the main thread to ensure correct ordering.
231                Handler mainThread = new Handler(Looper.getMainLooper());
232                mainThread.post(new Runnable() {
233                    @Override
234                    public void run() {
235                        mEventLog.add(tag, threadId);
236                        mEventLog.finish(this.toString());
237                    }
238                });
239                return;
240            }
241
242            mEventLog.add(tag, threadId);
243            mEventLog.finish(this.toString());
244        } else {
245            long requestTime = SystemClock.elapsedRealtime() - mRequestBirthTime;
246            if (requestTime >= SLOW_REQUEST_THRESHOLD_MS) {
247                VolleyLog.d("%d ms: %s", requestTime, this.toString());
248            }
249        }
250    }
251
252    /**
253     * Associates this request with the given queue. The request queue will be notified when this
254     * request has finished.
255     *
256     * @return This Request object to allow for chaining.
257     */
258    public Request<?> setRequestQueue(RequestQueue requestQueue) {
259        mRequestQueue = requestQueue;
260        return this;
261    }
262
263    /**
264     * Sets the sequence number of this request.  Used by {@link RequestQueue}.
265     *
266     * @return This Request object to allow for chaining.
267     */
268    public final Request<?> setSequence(int sequence) {
269        mSequence = sequence;
270        return this;
271    }
272
273    /**
274     * Returns the sequence number of this request.
275     */
276    public final int getSequence() {
277        if (mSequence == null) {
278            throw new IllegalStateException("getSequence called before setSequence");
279        }
280        return mSequence;
281    }
282
283    /**
284     * Returns the URL of this request.
285     */
286    public String getUrl() {
287        return mUrl;
288    }
289
290    /**
291     * Returns the cache key for this request.  By default, this is the URL.
292     */
293    public String getCacheKey() {
294        return getUrl();
295    }
296
297    /**
298     * Annotates this request with an entry retrieved for it from cache.
299     * Used for cache coherency support.
300     *
301     * @return This Request object to allow for chaining.
302     */
303    public Request<?> setCacheEntry(Cache.Entry entry) {
304        mCacheEntry = entry;
305        return this;
306    }
307
308    /**
309     * Returns the annotated cache entry, or null if there isn't one.
310     */
311    public Cache.Entry getCacheEntry() {
312        return mCacheEntry;
313    }
314
315    /**
316     * Mark this request as canceled.  No callback will be delivered.
317     */
318    public void cancel() {
319        mCanceled = true;
320    }
321
322    /**
323     * Returns true if this request has been canceled.
324     */
325    public boolean isCanceled() {
326        return mCanceled;
327    }
328
329    /**
330     * Returns a list of extra HTTP headers to go along with this request. Can
331     * throw {@link AuthFailureError} as authentication may be required to
332     * provide these values.
333     * @throws AuthFailureError In the event of auth failure
334     */
335    public Map<String, String> getHeaders() throws AuthFailureError {
336        return Collections.emptyMap();
337    }
338
339    /**
340     * Returns a Map of POST parameters to be used for this request, or null if
341     * a simple GET should be used.  Can throw {@link AuthFailureError} as
342     * authentication may be required to provide these values.
343     *
344     * <p>Note that only one of getPostParams() and getPostBody() can return a non-null
345     * value.</p>
346     * @throws AuthFailureError In the event of auth failure
347     *
348     * @deprecated Use {@link #getParams()} instead.
349     */
350    @Deprecated
351    protected Map<String, String> getPostParams() throws AuthFailureError {
352        return getParams();
353    }
354
355    /**
356     * Returns which encoding should be used when converting POST parameters returned by
357     * {@link #getPostParams()} into a raw POST body.
358     *
359     * <p>This controls both encodings:
360     * <ol>
361     *     <li>The string encoding used when converting parameter names and values into bytes prior
362     *         to URL encoding them.</li>
363     *     <li>The string encoding used when converting the URL encoded parameters into a raw
364     *         byte array.</li>
365     * </ol>
366     *
367     * @deprecated Use {@link #getParamsEncoding()} instead.
368     */
369    @Deprecated
370    protected String getPostParamsEncoding() {
371        return getParamsEncoding();
372    }
373
374    /**
375     * @deprecated Use {@link #getBodyContentType()} instead.
376     */
377    @Deprecated
378    public String getPostBodyContentType() {
379        return getBodyContentType();
380    }
381
382    /**
383     * Returns the raw POST body to be sent.
384     *
385     * @throws AuthFailureError In the event of auth failure
386     *
387     * @deprecated Use {@link #getBody()} instead.
388     */
389    @Deprecated
390    public byte[] getPostBody() throws AuthFailureError {
391        // Note: For compatibility with legacy clients of volley, this implementation must remain
392        // here instead of simply calling the getBody() function because this function must
393        // call getPostParams() and getPostParamsEncoding() since legacy clients would have
394        // overridden these two member functions for POST requests.
395        Map<String, String> postParams = getPostParams();
396        if (postParams != null && postParams.size() > 0) {
397            return encodeParameters(postParams, getPostParamsEncoding());
398        }
399        return null;
400    }
401
402    /**
403     * Returns a Map of parameters to be used for a POST or PUT request.  Can throw
404     * {@link AuthFailureError} as authentication may be required to provide these values.
405     *
406     * <p>Note that you can directly override {@link #getBody()} for custom data.</p>
407     *
408     * @throws AuthFailureError in the event of auth failure
409     */
410    protected Map<String, String> getParams() throws AuthFailureError {
411        return null;
412    }
413
414    /**
415     * Returns which encoding should be used when converting POST or PUT parameters returned by
416     * {@link #getParams()} into a raw POST or PUT body.
417     *
418     * <p>This controls both encodings:
419     * <ol>
420     *     <li>The string encoding used when converting parameter names and values into bytes prior
421     *         to URL encoding them.</li>
422     *     <li>The string encoding used when converting the URL encoded parameters into a raw
423     *         byte array.</li>
424     * </ol>
425     */
426    protected String getParamsEncoding() {
427        return DEFAULT_PARAMS_ENCODING;
428    }
429
430    /**
431     * Returns the content type of the POST or PUT body.
432     */
433    public String getBodyContentType() {
434        return "application/x-www-form-urlencoded; charset=" + getParamsEncoding();
435    }
436
437    /**
438     * Returns the raw POST or PUT body to be sent.
439     *
440     * <p>By default, the body consists of the request parameters in
441     * application/x-www-form-urlencoded format. When overriding this method, consider overriding
442     * {@link #getBodyContentType()} as well to match the new body format.
443     *
444     * @throws AuthFailureError in the event of auth failure
445     */
446    public byte[] getBody() throws AuthFailureError {
447        Map<String, String> params = getParams();
448        if (params != null && params.size() > 0) {
449            return encodeParameters(params, getParamsEncoding());
450        }
451        return null;
452    }
453
454    /**
455     * Converts <code>params</code> into an application/x-www-form-urlencoded encoded string.
456     */
457    private byte[] encodeParameters(Map<String, String> params, String paramsEncoding) {
458        StringBuilder encodedParams = new StringBuilder();
459        try {
460            for (Map.Entry<String, String> entry : params.entrySet()) {
461                encodedParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding));
462                encodedParams.append('=');
463                encodedParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding));
464                encodedParams.append('&');
465            }
466            return encodedParams.toString().getBytes(paramsEncoding);
467        } catch (UnsupportedEncodingException uee) {
468            throw new RuntimeException("Encoding not supported: " + paramsEncoding, uee);
469        }
470    }
471
472    /**
473     * Set whether or not responses to this request should be cached.
474     *
475     * @return This Request object to allow for chaining.
476     */
477    public final Request<?> setShouldCache(boolean shouldCache) {
478        mShouldCache = shouldCache;
479        return this;
480    }
481
482    /**
483     * Returns true if responses to this request should be cached.
484     */
485    public final boolean shouldCache() {
486        return mShouldCache;
487    }
488
489    /**
490     * Priority values.  Requests will be processed from higher priorities to
491     * lower priorities, in FIFO order.
492     */
493    public enum Priority {
494        LOW,
495        NORMAL,
496        HIGH,
497        IMMEDIATE
498    }
499
500    /**
501     * Returns the {@link Priority} of this request; {@link Priority#NORMAL} by default.
502     */
503    public Priority getPriority() {
504        return Priority.NORMAL;
505    }
506
507    /**
508     * Returns the socket timeout in milliseconds per retry attempt. (This value can be changed
509     * per retry attempt if a backoff is specified via backoffTimeout()). If there are no retry
510     * attempts remaining, this will cause delivery of a {@link TimeoutError} error.
511     */
512    public final int getTimeoutMs() {
513        return mRetryPolicy.getCurrentTimeout();
514    }
515
516    /**
517     * Returns the retry policy that should be used  for this request.
518     */
519    public RetryPolicy getRetryPolicy() {
520        return mRetryPolicy;
521    }
522
523    /**
524     * Mark this request as having a response delivered on it.  This can be used
525     * later in the request's lifetime for suppressing identical responses.
526     */
527    public void markDelivered() {
528        mResponseDelivered = true;
529    }
530
531    /**
532     * Returns true if this request has had a response delivered for it.
533     */
534    public boolean hasHadResponseDelivered() {
535        return mResponseDelivered;
536    }
537
538    /**
539     * Subclasses must implement this to parse the raw network response
540     * and return an appropriate response type. This method will be
541     * called from a worker thread.  The response will not be delivered
542     * if you return null.
543     * @param response Response from the network
544     * @return The parsed response, or null in the case of an error
545     */
546    abstract protected Response<T> parseNetworkResponse(NetworkResponse response);
547
548    /**
549     * Subclasses can override this method to parse 'networkError' and return a more specific error.
550     *
551     * <p>The default implementation just returns the passed 'networkError'.</p>
552     *
553     * @param volleyError the error retrieved from the network
554     * @return an NetworkError augmented with additional information
555     */
556    protected VolleyError parseNetworkError(VolleyError volleyError) {
557        return volleyError;
558    }
559
560    /**
561     * Subclasses must implement this to perform delivery of the parsed
562     * response to their listeners.  The given response is guaranteed to
563     * be non-null; responses that fail to parse are not delivered.
564     * @param response The parsed response returned by
565     * {@link #parseNetworkResponse(NetworkResponse)}
566     */
567    abstract protected void deliverResponse(T response);
568
569    /**
570     * Delivers error message to the ErrorListener that the Request was
571     * initialized with.
572     *
573     * @param error Error details
574     */
575    public void deliverError(VolleyError error) {
576        if (mErrorListener != null) {
577            mErrorListener.onErrorResponse(error);
578        }
579    }
580
581    /**
582     * Our comparator sorts from high to low priority, and secondarily by
583     * sequence number to provide FIFO ordering.
584     */
585    @Override
586    public int compareTo(Request<T> other) {
587        Priority left = this.getPriority();
588        Priority right = other.getPriority();
589
590        // High-priority requests are "lesser" so they are sorted to the front.
591        // Equal priorities are sorted by sequence number to provide FIFO ordering.
592        return left == right ?
593                this.mSequence - other.mSequence :
594                right.ordinal() - left.ordinal();
595    }
596
597    @Override
598    public String toString() {
599        String trafficStatsTag = "0x" + Integer.toHexString(getTrafficStatsTag());
600        return (mCanceled ? "[X] " : "[ ] ") + getUrl() + " " + trafficStatsTag + " "
601                + getPriority() + " " + mSequence;
602    }
603}
604