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