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