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