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