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