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