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 /** 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 Request(String url, Response.ErrorListener listener)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 */ Request(int method, String url, Response.ErrorListener listener)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 */ getMethod()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 */ setTag(Object tag)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 */ getTag()156 public Object getTag() { 157 return mTag; 158 } 159 160 /** 161 * @return this request's {@link com.android.volley.Response.ErrorListener}. 162 */ getErrorListener()163 public Response.ErrorListener getErrorListener() { 164 return mErrorListener; 165 } 166 167 /** 168 * @return A tag for use with {@link TrafficStats#setThreadStatsTag(int)} 169 */ getTrafficStatsTag()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 */ findDefaultTrafficStatsTag(String url)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 */ setRetryPolicy(RetryPolicy retryPolicy)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 */ addMarker(String tag)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 */ finish(final String tag)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 */ setRequestQueue(RequestQueue requestQueue)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 */ setSequence(int sequence)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 */ getSequence()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 */ getUrl()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 */ getCacheKey()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 */ setCacheEntry(Cache.Entry entry)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 */ getCacheEntry()298 public Cache.Entry getCacheEntry() { 299 return mCacheEntry; 300 } 301 302 /** 303 * Mark this request as canceled. No callback will be delivered. 304 */ cancel()305 public void cancel() { 306 mCanceled = true; 307 } 308 309 /** 310 * Returns true if this request has been canceled. 311 */ isCanceled()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 */ getHeaders()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 getPostParams()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 getPostParamsEncoding()357 protected String getPostParamsEncoding() { 358 return getParamsEncoding(); 359 } 360 361 /** 362 * @deprecated Use {@link #getBodyContentType()} instead. 363 */ 364 @Deprecated getPostBodyContentType()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 getPostBody()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 */ getParams()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 */ getParamsEncoding()413 protected String getParamsEncoding() { 414 return DEFAULT_PARAMS_ENCODING; 415 } 416 417 /** 418 * Returns the content type of the POST or PUT body. 419 */ getBodyContentType()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 */ getBody()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 */ encodeParameters(Map<String, String> params, String paramsEncoding)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 */ setShouldCache(boolean shouldCache)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 */ shouldCache()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 */ getPriority()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 */ getTimeoutMs()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 */ getRetryPolicy()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 */ markDelivered()514 public void markDelivered() { 515 mResponseDelivered = true; 516 } 517 518 /** 519 * Returns true if this request has had a response delivered for it. 520 */ hasHadResponseDelivered()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 */ parseNetworkResponse(NetworkResponse response)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 */ parseNetworkError(VolleyError volleyError)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 */ deliverResponse(T response)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 */ deliverError(VolleyError error)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 compareTo(Request<T> other)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 toString()585 public String toString() { 586 String trafficStatsTag = "0x" + Integer.toHexString(getTrafficStatsTag()); 587 return (mCanceled ? "[X] " : "[ ] ") + getUrl() + " " + trafficStatsTag + " " 588 + getPriority() + " " + mSequence; 589 } 590 } 591