| OLD | NEW |
| (Empty) |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 package org.chromium.net; | |
| 6 | |
| 7 import android.content.Context; | |
| 8 import android.text.TextUtils; | |
| 9 | |
| 10 import java.io.FileNotFoundException; | |
| 11 import java.io.IOException; | |
| 12 import java.io.InputStream; | |
| 13 import java.io.OutputStream; | |
| 14 import java.net.HttpURLConnection; | |
| 15 import java.net.ProtocolException; | |
| 16 import java.net.URL; | |
| 17 import java.nio.ByteBuffer; | |
| 18 import java.nio.channels.ReadableByteChannel; | |
| 19 import java.nio.channels.WritableByteChannel; | |
| 20 import java.util.List; | |
| 21 import java.util.Map; | |
| 22 import java.util.Map.Entry; | |
| 23 import java.util.concurrent.ExecutorService; | |
| 24 import java.util.concurrent.Executors; | |
| 25 import java.util.concurrent.ThreadFactory; | |
| 26 import java.util.concurrent.atomic.AtomicInteger; | |
| 27 import java.util.zip.GZIPInputStream; | |
| 28 | |
| 29 /** | |
| 30 * Network request using the HttpUrlConnection implementation. | |
| 31 * @deprecated Use {@link UrlRequest} instead. | |
| 32 * {@hide as it's deprecated} | |
| 33 */ | |
| 34 @Deprecated | |
| 35 class HttpUrlConnectionUrlRequest implements HttpUrlRequest { | |
| 36 | |
| 37 private static final int MAX_CHUNK_SIZE = 8192; | |
| 38 | |
| 39 private static final int CONNECT_TIMEOUT = 3000; | |
| 40 | |
| 41 private static final int READ_TIMEOUT = 90000; | |
| 42 | |
| 43 private final Context mContext; | |
| 44 | |
| 45 private final String mDefaultUserAgent; | |
| 46 | |
| 47 private final String mUrl; | |
| 48 | |
| 49 private final Map<String, String> mHeaders; | |
| 50 | |
| 51 private final WritableByteChannel mSink; | |
| 52 | |
| 53 private final HttpUrlRequestListener mListener; | |
| 54 | |
| 55 private IOException mException; | |
| 56 | |
| 57 private HttpURLConnection mConnection; | |
| 58 | |
| 59 private long mOffset; | |
| 60 | |
| 61 private int mContentLength; | |
| 62 | |
| 63 private int mUploadContentLength; | |
| 64 | |
| 65 private long mContentLengthLimit; | |
| 66 | |
| 67 private boolean mCancelIfContentLengthOverLimit; | |
| 68 | |
| 69 private boolean mContentLengthOverLimit; | |
| 70 | |
| 71 private boolean mSkippingToOffset; | |
| 72 | |
| 73 private long mSize; | |
| 74 | |
| 75 private String mPostContentType; | |
| 76 | |
| 77 private byte[] mPostData; | |
| 78 | |
| 79 private ReadableByteChannel mPostDataChannel; | |
| 80 | |
| 81 private String mContentType; | |
| 82 | |
| 83 private int mHttpStatusCode; | |
| 84 | |
| 85 private String mHttpStatusText; | |
| 86 | |
| 87 private boolean mStarted; | |
| 88 | |
| 89 private boolean mCanceled; | |
| 90 | |
| 91 private String mMethod; | |
| 92 | |
| 93 private InputStream mResponseStream; | |
| 94 | |
| 95 private final Object mLock; | |
| 96 | |
| 97 private static ExecutorService sExecutorService; | |
| 98 | |
| 99 private static final Object sExecutorServiceLock = new Object(); | |
| 100 | |
| 101 HttpUrlConnectionUrlRequest(Context context, String defaultUserAgent, | |
| 102 String url, int requestPriority, Map<String, String> headers, | |
| 103 HttpUrlRequestListener listener) { | |
| 104 this(context, defaultUserAgent, url, requestPriority, headers, | |
| 105 new ChunkedWritableByteChannel(), listener); | |
| 106 } | |
| 107 | |
| 108 HttpUrlConnectionUrlRequest(Context context, String defaultUserAgent, | |
| 109 String url, int requestPriority, Map<String, String> headers, | |
| 110 WritableByteChannel sink, HttpUrlRequestListener listener) { | |
| 111 if (context == null) { | |
| 112 throw new NullPointerException("Context is required"); | |
| 113 } | |
| 114 if (url == null) { | |
| 115 throw new NullPointerException("URL is required"); | |
| 116 } | |
| 117 mContext = context; | |
| 118 mDefaultUserAgent = defaultUserAgent; | |
| 119 mUrl = url; | |
| 120 mHeaders = headers; | |
| 121 mSink = sink; | |
| 122 mListener = listener; | |
| 123 mLock = new Object(); | |
| 124 } | |
| 125 | |
| 126 private static ExecutorService getExecutor() { | |
| 127 synchronized (sExecutorServiceLock) { | |
| 128 if (sExecutorService == null) { | |
| 129 ThreadFactory threadFactory = new ThreadFactory() { | |
| 130 private final AtomicInteger mCount = new AtomicInteger(1); | |
| 131 | |
| 132 @Override | |
| 133 public Thread newThread(Runnable r) { | |
| 134 Thread thread = new Thread(r, | |
| 135 "HttpUrlConnection #" | |
| 136 + mCount.getAndIncrement()); | |
| 137 // Note that this thread is not doing actual networking. | |
| 138 // It's only a controller. | |
| 139 thread.setPriority(Thread.NORM_PRIORITY); | |
| 140 return thread; | |
| 141 } | |
| 142 }; | |
| 143 sExecutorService = Executors.newCachedThreadPool(threadFactory); | |
| 144 } | |
| 145 return sExecutorService; | |
| 146 } | |
| 147 } | |
| 148 | |
| 149 @Override | |
| 150 public String getUrl() { | |
| 151 return mUrl; | |
| 152 } | |
| 153 | |
| 154 @Override | |
| 155 public void setOffset(long offset) { | |
| 156 mOffset = offset; | |
| 157 } | |
| 158 | |
| 159 @Override | |
| 160 public void setContentLengthLimit(long limit, boolean cancelEarly) { | |
| 161 mContentLengthLimit = limit; | |
| 162 mCancelIfContentLengthOverLimit = cancelEarly; | |
| 163 } | |
| 164 | |
| 165 @Override | |
| 166 public void setUploadData(String contentType, byte[] data) { | |
| 167 validateNotStarted(); | |
| 168 mPostContentType = contentType; | |
| 169 mPostData = data; | |
| 170 mPostDataChannel = null; | |
| 171 } | |
| 172 | |
| 173 @Override | |
| 174 public void setUploadChannel(String contentType, | |
| 175 ReadableByteChannel channel, long contentLength) { | |
| 176 validateNotStarted(); | |
| 177 if (contentLength > Integer.MAX_VALUE) { | |
| 178 throw new IllegalArgumentException( | |
| 179 "Upload contentLength is too big."); | |
| 180 } | |
| 181 mUploadContentLength = (int) contentLength; | |
| 182 mPostContentType = contentType; | |
| 183 mPostDataChannel = channel; | |
| 184 mPostData = null; | |
| 185 } | |
| 186 | |
| 187 | |
| 188 @Override | |
| 189 public void setHttpMethod(String method) { | |
| 190 validateNotStarted(); | |
| 191 mMethod = method; | |
| 192 } | |
| 193 | |
| 194 @Override | |
| 195 public void disableRedirects() { | |
| 196 validateNotStarted(); | |
| 197 HttpURLConnection.setFollowRedirects(false); | |
| 198 } | |
| 199 | |
| 200 @Override | |
| 201 public void start() { | |
| 202 getExecutor().execute(new Runnable() { | |
| 203 @Override | |
| 204 public void run() { | |
| 205 startOnExecutorThread(); | |
| 206 } | |
| 207 }); | |
| 208 } | |
| 209 | |
| 210 private void startOnExecutorThread() { | |
| 211 boolean readingResponse = false; | |
| 212 try { | |
| 213 synchronized (mLock) { | |
| 214 if (mCanceled) { | |
| 215 return; | |
| 216 } | |
| 217 } | |
| 218 | |
| 219 URL url = new URL(mUrl); | |
| 220 mConnection = (HttpURLConnection) url.openConnection(); | |
| 221 // If configured, use the provided http verb. | |
| 222 if (mMethod != null) { | |
| 223 try { | |
| 224 mConnection.setRequestMethod(mMethod); | |
| 225 } catch (ProtocolException e) { | |
| 226 // Since request hasn't started earlier, it | |
| 227 // must be an illegal HTTP verb. | |
| 228 throw new IllegalArgumentException(e); | |
| 229 } | |
| 230 } | |
| 231 mConnection.setConnectTimeout(CONNECT_TIMEOUT); | |
| 232 mConnection.setReadTimeout(READ_TIMEOUT); | |
| 233 mConnection.setInstanceFollowRedirects(true); | |
| 234 if (mHeaders != null) { | |
| 235 for (Entry<String, String> header : mHeaders.entrySet()) { | |
| 236 mConnection.setRequestProperty(header.getKey(), | |
| 237 header.getValue()); | |
| 238 } | |
| 239 } | |
| 240 | |
| 241 if (mOffset != 0) { | |
| 242 mConnection.setRequestProperty("Range", | |
| 243 "bytes=" + mOffset + "-"); | |
| 244 } | |
| 245 | |
| 246 if (mConnection.getRequestProperty("User-Agent") == null) { | |
| 247 mConnection.setRequestProperty("User-Agent", mDefaultUserAgent); | |
| 248 } | |
| 249 | |
| 250 if (mPostData != null || mPostDataChannel != null) { | |
| 251 uploadData(); | |
| 252 } | |
| 253 | |
| 254 InputStream stream = null; | |
| 255 try { | |
| 256 // We need to open the stream before asking for the response | |
| 257 // code. | |
| 258 stream = mConnection.getInputStream(); | |
| 259 } catch (FileNotFoundException ex) { | |
| 260 // Ignore - the response has no body. | |
| 261 } | |
| 262 | |
| 263 mHttpStatusCode = mConnection.getResponseCode(); | |
| 264 mHttpStatusText = mConnection.getResponseMessage(); | |
| 265 mContentType = mConnection.getContentType(); | |
| 266 mContentLength = mConnection.getContentLength(); | |
| 267 if (mContentLengthLimit > 0 && mContentLength > mContentLengthLimit | |
| 268 && mCancelIfContentLengthOverLimit) { | |
| 269 onContentLengthOverLimit(); | |
| 270 return; | |
| 271 } | |
| 272 | |
| 273 mListener.onResponseStarted(this); | |
| 274 | |
| 275 mResponseStream = isError(mHttpStatusCode) ? mConnection | |
| 276 .getErrorStream() | |
| 277 : stream; | |
| 278 | |
| 279 if (mResponseStream != null | |
| 280 && "gzip".equals(mConnection.getContentEncoding())) { | |
| 281 mResponseStream = new GZIPInputStream(mResponseStream); | |
| 282 mContentLength = -1; | |
| 283 } | |
| 284 | |
| 285 if (mOffset != 0) { | |
| 286 // The server may ignore the request for a byte range. | |
| 287 if (mHttpStatusCode == HttpURLConnection.HTTP_OK) { | |
| 288 if (mContentLength != -1) { | |
| 289 mContentLength -= mOffset; | |
| 290 } | |
| 291 mSkippingToOffset = true; | |
| 292 } else { | |
| 293 mSize = mOffset; | |
| 294 } | |
| 295 } | |
| 296 | |
| 297 if (mResponseStream != null) { | |
| 298 readingResponse = true; | |
| 299 readResponseAsync(); | |
| 300 } | |
| 301 } catch (IOException e) { | |
| 302 mException = e; | |
| 303 } finally { | |
| 304 if (mPostDataChannel != null) { | |
| 305 try { | |
| 306 mPostDataChannel.close(); | |
| 307 } catch (IOException e) { | |
| 308 // Ignore | |
| 309 } | |
| 310 } | |
| 311 | |
| 312 // Don't call onRequestComplete yet if we are reading the response | |
| 313 // on a separate thread | |
| 314 if (!readingResponse) { | |
| 315 mListener.onRequestComplete(this); | |
| 316 } | |
| 317 } | |
| 318 } | |
| 319 | |
| 320 private void uploadData() throws IOException { | |
| 321 mConnection.setDoOutput(true); | |
| 322 if (!TextUtils.isEmpty(mPostContentType)) { | |
| 323 mConnection.setRequestProperty("Content-Type", mPostContentType); | |
| 324 } | |
| 325 | |
| 326 OutputStream uploadStream = null; | |
| 327 try { | |
| 328 if (mPostData != null) { | |
| 329 mConnection.setFixedLengthStreamingMode(mPostData.length); | |
| 330 uploadStream = mConnection.getOutputStream(); | |
| 331 uploadStream.write(mPostData); | |
| 332 } else { | |
| 333 mConnection.setFixedLengthStreamingMode(mUploadContentLength); | |
| 334 uploadStream = mConnection.getOutputStream(); | |
| 335 byte[] bytes = new byte[MAX_CHUNK_SIZE]; | |
| 336 ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); | |
| 337 while (mPostDataChannel.read(byteBuffer) > 0) { | |
| 338 byteBuffer.flip(); | |
| 339 uploadStream.write(bytes, 0, byteBuffer.limit()); | |
| 340 byteBuffer.clear(); | |
| 341 } | |
| 342 } | |
| 343 } finally { | |
| 344 if (uploadStream != null) { | |
| 345 uploadStream.close(); | |
| 346 } | |
| 347 } | |
| 348 } | |
| 349 | |
| 350 private void readResponseAsync() { | |
| 351 getExecutor().execute(new Runnable() { | |
| 352 @Override | |
| 353 public void run() { | |
| 354 readResponse(); | |
| 355 } | |
| 356 }); | |
| 357 } | |
| 358 | |
| 359 private void readResponse() { | |
| 360 try { | |
| 361 if (mResponseStream != null) { | |
| 362 readResponseStream(); | |
| 363 } | |
| 364 } catch (IOException e) { | |
| 365 mException = e; | |
| 366 } finally { | |
| 367 try { | |
| 368 mConnection.disconnect(); | |
| 369 } catch (ArrayIndexOutOfBoundsException t) { | |
| 370 // Ignore it. | |
| 371 } | |
| 372 | |
| 373 try { | |
| 374 mSink.close(); | |
| 375 } catch (IOException e) { | |
| 376 if (mException == null) { | |
| 377 mException = e; | |
| 378 } | |
| 379 } | |
| 380 } | |
| 381 mListener.onRequestComplete(this); | |
| 382 } | |
| 383 | |
| 384 private void readResponseStream() throws IOException { | |
| 385 byte[] buffer = new byte[MAX_CHUNK_SIZE]; | |
| 386 int size; | |
| 387 while (!isCanceled() && (size = mResponseStream.read(buffer)) != -1) { | |
| 388 int start = 0; | |
| 389 int count = size; | |
| 390 mSize += size; | |
| 391 if (mSkippingToOffset) { | |
| 392 if (mSize <= mOffset) { | |
| 393 continue; | |
| 394 } else { | |
| 395 mSkippingToOffset = false; | |
| 396 start = (int) (mOffset - (mSize - size)); | |
| 397 count -= start; | |
| 398 } | |
| 399 } | |
| 400 | |
| 401 if (mContentLengthLimit != 0 && mSize > mContentLengthLimit) { | |
| 402 count -= (int) (mSize - mContentLengthLimit); | |
| 403 if (count > 0) { | |
| 404 mSink.write(ByteBuffer.wrap(buffer, start, count)); | |
| 405 } | |
| 406 onContentLengthOverLimit(); | |
| 407 return; | |
| 408 } | |
| 409 | |
| 410 mSink.write(ByteBuffer.wrap(buffer, start, count)); | |
| 411 } | |
| 412 } | |
| 413 | |
| 414 @Override | |
| 415 public void cancel() { | |
| 416 synchronized (mLock) { | |
| 417 if (mCanceled) { | |
| 418 return; | |
| 419 } | |
| 420 | |
| 421 mCanceled = true; | |
| 422 } | |
| 423 } | |
| 424 | |
| 425 @Override | |
| 426 public boolean isCanceled() { | |
| 427 synchronized (mLock) { | |
| 428 return mCanceled; | |
| 429 } | |
| 430 } | |
| 431 | |
| 432 @Override | |
| 433 public String getNegotiatedProtocol() { | |
| 434 return ""; | |
| 435 } | |
| 436 | |
| 437 @Override | |
| 438 public boolean wasCached() { | |
| 439 return false; | |
| 440 } | |
| 441 | |
| 442 @Override | |
| 443 public int getHttpStatusCode() { | |
| 444 int httpStatusCode = mHttpStatusCode; | |
| 445 | |
| 446 // If we have been able to successfully resume a previously interrupted | |
| 447 // download, | |
| 448 // the status code will be 206, not 200. Since the rest of the | |
| 449 // application is | |
| 450 // expecting 200 to indicate success, we need to fake it. | |
| 451 if (httpStatusCode == HttpURLConnection.HTTP_PARTIAL) { | |
| 452 httpStatusCode = HttpURLConnection.HTTP_OK; | |
| 453 } | |
| 454 return httpStatusCode; | |
| 455 } | |
| 456 | |
| 457 @Override | |
| 458 public String getHttpStatusText() { | |
| 459 return mHttpStatusText; | |
| 460 } | |
| 461 | |
| 462 @Override | |
| 463 public IOException getException() { | |
| 464 if (mException == null && mContentLengthOverLimit) { | |
| 465 mException = new ResponseTooLargeException(); | |
| 466 } | |
| 467 return mException; | |
| 468 } | |
| 469 | |
| 470 private void onContentLengthOverLimit() { | |
| 471 mContentLengthOverLimit = true; | |
| 472 cancel(); | |
| 473 } | |
| 474 | |
| 475 private static boolean isError(int statusCode) { | |
| 476 return (statusCode / 100) != 2; | |
| 477 } | |
| 478 | |
| 479 /** | |
| 480 * Returns the response as a ByteBuffer. | |
| 481 */ | |
| 482 @Override | |
| 483 public ByteBuffer getByteBuffer() { | |
| 484 return ((ChunkedWritableByteChannel) mSink).getByteBuffer(); | |
| 485 } | |
| 486 | |
| 487 @Override | |
| 488 public byte[] getResponseAsBytes() { | |
| 489 return ((ChunkedWritableByteChannel) mSink).getBytes(); | |
| 490 } | |
| 491 | |
| 492 @Override | |
| 493 public long getContentLength() { | |
| 494 return mContentLength; | |
| 495 } | |
| 496 | |
| 497 @Override | |
| 498 public String getContentType() { | |
| 499 return mContentType; | |
| 500 } | |
| 501 | |
| 502 @Override | |
| 503 public String getHeader(String name) { | |
| 504 if (mConnection == null) { | |
| 505 throw new IllegalStateException("Response headers not available"); | |
| 506 } | |
| 507 Map<String, List<String>> headerFields = mConnection.getHeaderFields(); | |
| 508 if (headerFields != null) { | |
| 509 List<String> headerValues = headerFields.get(name); | |
| 510 if (headerValues != null) { | |
| 511 return TextUtils.join(", ", headerValues); | |
| 512 } | |
| 513 } | |
| 514 return null; | |
| 515 } | |
| 516 | |
| 517 @Override | |
| 518 public Map<String, List<String>> getAllHeaders() { | |
| 519 if (mConnection == null) { | |
| 520 throw new IllegalStateException("Response headers not available"); | |
| 521 } | |
| 522 return mConnection.getHeaderFields(); | |
| 523 } | |
| 524 | |
| 525 private void validateNotStarted() { | |
| 526 if (mStarted) { | |
| 527 throw new IllegalStateException("Request already started"); | |
| 528 } | |
| 529 } | |
| 530 } | |
| OLD | NEW |