[go: nahoru, domu]

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