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