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