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