OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package org.chromium.net; | 5 package org.chromium.net; |
6 | 6 |
7 import android.util.Log; | 7 import android.util.Log; |
8 | 8 |
9 import org.apache.http.conn.ConnectTimeoutException; | 9 import org.apache.http.conn.ConnectTimeoutException; |
10 import org.chromium.base.CalledByNative; | 10 import org.chromium.base.CalledByNative; |
11 import org.chromium.base.JNINamespace; | 11 import org.chromium.base.JNINamespace; |
12 | 12 |
13 import java.io.IOException; | 13 import java.io.IOException; |
14 import java.net.MalformedURLException; | 14 import java.net.MalformedURLException; |
15 import java.net.URL; | 15 import java.net.URL; |
16 import java.net.UnknownHostException; | 16 import java.net.UnknownHostException; |
17 import java.nio.ByteBuffer; | 17 import java.nio.ByteBuffer; |
18 import java.nio.channels.ReadableByteChannel; | 18 import java.nio.channels.ReadableByteChannel; |
19 import java.nio.channels.WritableByteChannel; | 19 import java.nio.channels.WritableByteChannel; |
20 import java.util.ArrayList; | 20 import java.util.ArrayList; |
21 import java.util.HashMap; | 21 import java.util.HashMap; |
22 import java.util.List; | 22 import java.util.List; |
23 import java.util.Map; | 23 import java.util.Map; |
24 import java.util.Map.Entry; | 24 import java.util.Map.Entry; |
| 25 import java.util.concurrent.Semaphore; |
25 | 26 |
26 /** | 27 /** |
27 * Network request using the native http stack implementation. | 28 * Network request using the native http stack implementation. |
28 */ | 29 */ |
29 @JNINamespace("cronet") | 30 @JNINamespace("cronet") |
30 public class ChromiumUrlRequest implements HttpUrlRequest { | 31 public class ChromiumUrlRequest implements HttpUrlRequest { |
31 /** | 32 /** |
32 * Native adapter object, owned by UrlRequest. | 33 * Native adapter object, owned by UrlRequest. |
33 */ | 34 */ |
34 private long mUrlRequestAdapter; | 35 private long mUrlRequestAdapter; |
35 private final ChromiumUrlRequestContext mRequestContext; | 36 private final ChromiumUrlRequestContext mRequestContext; |
36 private final String mUrl; | 37 private final String mUrl; |
37 private final int mPriority; | 38 private final int mPriority; |
38 private final Map<String, String> mHeaders; | 39 private final Map<String, String> mHeaders; |
39 private final WritableByteChannel mSink; | 40 private final WritableByteChannel mSink; |
40 private Map<String, String> mAdditionalHeaders; | 41 private Map<String, String> mAdditionalHeaders; |
41 private String mUploadContentType; | 42 private String mUploadContentType; |
42 private String mMethod; | 43 private String mMethod; |
43 private byte[] mUploadData; | 44 private byte[] mUploadData; |
| 45 private Semaphore mAppendChunkSemaphore; |
44 private ReadableByteChannel mUploadChannel; | 46 private ReadableByteChannel mUploadChannel; |
45 private WritableByteChannel mOutputChannel; | 47 private WritableByteChannel mOutputChannel; |
46 private IOException mSinkException; | 48 private IOException mSinkException; |
47 private volatile boolean mStarted; | 49 private volatile boolean mStarted; |
48 private volatile boolean mCanceled; | 50 private volatile boolean mCanceled; |
49 private volatile boolean mRecycled; | 51 private volatile boolean mRecycled; |
50 private volatile boolean mFinished; | 52 private volatile boolean mFinished; |
51 private boolean mHeadersAvailable; | 53 private boolean mHeadersAvailable; |
52 private String mContentType; | 54 private String mContentType; |
53 private long mUploadContentLength; | 55 private long mUploadContentLength; |
(...skipping 159 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
213 * @param contentType MIME type of the upload content or null if this is not | 215 * @param contentType MIME type of the upload content or null if this is not |
214 * an upload. | 216 * an upload. |
215 * @param data The content that needs to be uploaded. | 217 * @param data The content that needs to be uploaded. |
216 */ | 218 */ |
217 public void setUploadData(String contentType, byte[] data) { | 219 public void setUploadData(String contentType, byte[] data) { |
218 synchronized (mLock) { | 220 synchronized (mLock) { |
219 validateNotStarted(); | 221 validateNotStarted(); |
220 mUploadContentType = contentType; | 222 mUploadContentType = contentType; |
221 mUploadData = data; | 223 mUploadData = data; |
222 mUploadChannel = null; | 224 mUploadChannel = null; |
| 225 mAppendChunkSemaphore = null; |
223 } | 226 } |
224 } | 227 } |
225 | 228 |
226 /** | 229 /** |
227 * Sets a readable byte channel to upload as part of a POST or PUT request. | 230 * Sets a readable byte channel to upload as part of a POST or PUT request. |
228 * | 231 * |
229 * @param contentType MIME type of the upload content or null if this is not | 232 * @param contentType MIME type of the upload content or null if this is not |
230 * an upload request. | 233 * an upload request. |
231 * @param channel The channel to read to read upload data from if this is an | 234 * @param channel The channel to read to read upload data from if this is an |
232 * upload request. | 235 * upload request. |
233 * @param contentLength The length of data to upload. | 236 * @param contentLength The length of data to upload. |
234 */ | 237 */ |
235 public void setUploadChannel(String contentType, | 238 public void setUploadChannel(String contentType, |
236 ReadableByteChannel channel, long contentLength) { | 239 ReadableByteChannel channel, long contentLength) { |
237 synchronized (mLock) { | 240 synchronized (mLock) { |
238 validateNotStarted(); | 241 validateNotStarted(); |
239 mUploadContentType = contentType; | 242 mUploadContentType = contentType; |
240 mUploadChannel = channel; | 243 mUploadChannel = channel; |
241 mUploadContentLength = contentLength; | 244 mUploadContentLength = contentLength; |
242 mUploadData = null; | 245 mUploadData = null; |
| 246 mAppendChunkSemaphore = null; |
243 } | 247 } |
244 } | 248 } |
245 | 249 |
| 250 /** |
| 251 * Creates writable byte channel for chunked uploads as part of a POST or |
| 252 * PUT request. |
| 253 * |
| 254 * @param contentType MIME type of the upload content. |
| 255 */ |
| 256 public WritableByteChannel createChunkedUploadChannel(String contentType) { |
| 257 synchronized (mLock) { |
| 258 validateNotStarted(); |
| 259 mUploadContentType = contentType; |
| 260 mUploadChannel = null; |
| 261 mUploadContentLength = 0; |
| 262 mUploadData = null; |
| 263 mAppendChunkSemaphore = new Semaphore(0); |
| 264 } |
| 265 return new UploadChannel(); |
| 266 } |
| 267 |
246 /** | 268 /** |
247 * Sets HTTP method for upload request. Only PUT or POST are allowed. | 269 * Sets HTTP method for upload request. Only PUT or POST are allowed. |
248 */ | 270 */ |
249 public void setHttpMethod(String method) { | 271 public void setHttpMethod(String method) { |
250 validateNotStarted(); | 272 validateNotStarted(); |
251 if (!("PUT".equals(method) || "POST".equals(method))) { | 273 if (!("PUT".equals(method) || "POST".equals(method))) { |
252 throw new IllegalArgumentException("Only PUT or POST are allowed."); | 274 throw new IllegalArgumentException("Only PUT or POST are allowed."); |
253 } | 275 } |
254 mMethod = method; | 276 mMethod = method; |
255 } | 277 } |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
296 entry.getValue()); | 318 entry.getValue()); |
297 } | 319 } |
298 } | 320 } |
299 | 321 |
300 if (mUploadData != null && mUploadData.length > 0) { | 322 if (mUploadData != null && mUploadData.length > 0) { |
301 nativeSetUploadData(mUrlRequestAdapter, mUploadContentType, | 323 nativeSetUploadData(mUrlRequestAdapter, mUploadContentType, |
302 mUploadData); | 324 mUploadData); |
303 } else if (mUploadChannel != null) { | 325 } else if (mUploadChannel != null) { |
304 nativeSetUploadChannel(mUrlRequestAdapter, mUploadContentType, | 326 nativeSetUploadChannel(mUrlRequestAdapter, mUploadContentType, |
305 mUploadContentLength); | 327 mUploadContentLength); |
| 328 } else if (mUploadContentType != null) { |
| 329 nativeEnableChunkedUpload(mUrlRequestAdapter, |
| 330 mUploadContentType); |
306 } | 331 } |
307 | 332 |
308 nativeStart(mUrlRequestAdapter); | 333 nativeStart(mUrlRequestAdapter); |
309 } | 334 } |
310 } | 335 } |
311 | 336 |
312 public void cancel() { | 337 public void cancel() { |
313 synchronized (mLock) { | 338 synchronized (mLock) { |
314 if (mCanceled) { | 339 if (mCanceled) { |
315 return; | 340 return; |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
371 default: | 396 default: |
372 return ChromiumUrlRequestPriority.MEDIUM; | 397 return ChromiumUrlRequestPriority.MEDIUM; |
373 } | 398 } |
374 } | 399 } |
375 | 400 |
376 private void onContentLengthOverLimit() { | 401 private void onContentLengthOverLimit() { |
377 mContentLengthOverLimit = true; | 402 mContentLengthOverLimit = true; |
378 cancel(); | 403 cancel(); |
379 } | 404 } |
380 | 405 |
| 406 /** |
| 407 * Invokes {@link #nativeAppendChunk(long, ByteBuffer, int, boolean)} and |
| 408 * waits for it to notify completion. |
| 409 * @param chunk The data. It's position must be zero. |
| 410 * @param isLastChunk Whether chunk is the last one. |
| 411 */ |
| 412 void appendChunkBlocking(ByteBuffer chunk, boolean isLastChunk) |
| 413 throws IOException { |
| 414 if (chunk.position() != 0) { |
| 415 throw new IllegalArgumentException("The position must be zero."); |
| 416 } |
| 417 synchronized (mLock) { |
| 418 if (mUrlRequestAdapter == 0) { |
| 419 throw new IOException("Native adapter is destroyed."); |
| 420 } |
| 421 nativeAppendChunkToUpload(mUrlRequestAdapter, chunk, chunk.limit(), |
| 422 false); |
| 423 // Wait for the data to be actually consumed. |
| 424 try { |
| 425 mAppendChunkSemaphore.acquire(); |
| 426 } catch (InterruptedException e) { |
| 427 // We were interrupted before the data was uploaded. Recovering |
| 428 // from this state is complicated so we cancel the upload |
| 429 // operation and fail. |
| 430 Thread.currentThread().interrupt(); |
| 431 |
| 432 // TODO(miloslav): Not sure why do we set mSinkException here. |
| 433 mSinkException = new IOException("Upload interrupted", e); |
| 434 cancel(); |
| 435 throw mSinkException; |
| 436 } |
| 437 } |
| 438 } |
| 439 |
381 /** | 440 /** |
382 * A callback invoked when the response has been fully consumed. | 441 * A callback invoked when the response has been fully consumed. |
383 */ | 442 */ |
384 private void onRequestComplete() { | 443 private void onRequestComplete() { |
385 mListener.onRequestComplete(this); | 444 mListener.onRequestComplete(this); |
386 } | 445 } |
387 | 446 |
388 private void validateNotRecycled() { | 447 private void validateNotRecycled() { |
389 if (mRecycled) { | 448 if (mRecycled) { |
390 throw new IllegalStateException("Accessing recycled request"); | 449 throw new IllegalStateException("Accessing recycled request"); |
391 } | 450 } |
392 } | 451 } |
393 | 452 |
394 private void validateNotStarted() { | 453 private void validateNotStarted() { |
395 if (mStarted) { | 454 if (mStarted) { |
396 throw new IllegalStateException("Request already started"); | 455 throw new IllegalStateException("Request already started"); |
397 } | 456 } |
398 } | 457 } |
399 | 458 |
400 private void validateHeadersAvailable() { | 459 private void validateHeadersAvailable() { |
401 if (!mHeadersAvailable) { | 460 if (!mHeadersAvailable) { |
402 throw new IllegalStateException("Response headers not available"); | 461 throw new IllegalStateException("Response headers not available"); |
403 } | 462 } |
404 } | 463 } |
405 | 464 |
| 465 class UploadChannel implements WritableByteChannel { |
| 466 private static final int UPLOAD_BYTE_BUFFER_SIZE = 32768; |
| 467 |
| 468 private boolean mOpen = true; |
| 469 // Native wants a direct buffer. |
| 470 private final ByteBuffer mBuffer = |
| 471 ByteBuffer.allocateDirect(UPLOAD_BYTE_BUFFER_SIZE); |
| 472 |
| 473 @Override |
| 474 public synchronized boolean isOpen() { |
| 475 return mOpen; |
| 476 } |
| 477 |
| 478 @Override |
| 479 public synchronized void close() throws IOException { |
| 480 Log.d(ChromiumUrlRequestContext.LOG_TAG, |
| 481 "UploadChannel.close() url=" + getUrl()); |
| 482 if (!mOpen) { |
| 483 return; |
| 484 } |
| 485 |
| 486 mOpen = false; |
| 487 mBuffer.clear(); |
| 488 |
| 489 // NOOP If the native peer has been destroyed. |
| 490 try { |
| 491 Log.d(ChromiumUrlRequestContext.LOG_TAG, |
| 492 "UploadChannel.close(): final chunk."); |
| 493 appendChunkBlocking(mBuffer, true); |
| 494 } catch (IOException e) { |
| 495 Log.w(ChromiumUrlRequestContext.LOG_TAG, |
| 496 "Ignoring exception during closing.", e); |
| 497 } |
| 498 Log.d(ChromiumUrlRequestContext.LOG_TAG, |
| 499 "UploadChannel.close() done."); |
| 500 } |
| 501 |
| 502 @Override |
| 503 public synchronized int write(ByteBuffer sourceBuffer) |
| 504 throws IOException { |
| 505 Log.d(ChromiumUrlRequestContext.LOG_TAG, "UploadChannel.write(" + |
| 506 sourceBuffer.remaining() + " bytes) url=" + getUrl()); |
| 507 int written = 0; |
| 508 while (sourceBuffer.hasRemaining()) { |
| 509 mBuffer.clear(); |
| 510 int oldLimit = sourceBuffer.limit(); |
| 511 if (sourceBuffer.remaining() > mBuffer.remaining()) { |
| 512 sourceBuffer.limit(sourceBuffer.position() |
| 513 + mBuffer.remaining()); |
| 514 } |
| 515 mBuffer.put(sourceBuffer); |
| 516 mBuffer.flip(); |
| 517 written += mBuffer.limit(); |
| 518 appendChunkBlocking(mBuffer, false); |
| 519 sourceBuffer.limit(oldLimit); |
| 520 } |
| 521 Log.d(ChromiumUrlRequestContext.LOG_TAG, |
| 522 "UploadChannel.write() returning"); |
| 523 return written; |
| 524 } |
| 525 } |
| 526 |
406 // Private methods called by native library. | 527 // Private methods called by native library. |
407 | 528 |
408 /** | 529 /** |
409 * If @CalledByNative method throws an exception, request gets cancelled | 530 * If @CalledByNative method throws an exception, request gets cancelled |
410 * and exception could be retrieved from using getException(). | 531 * and exception could be retrieved from using getException(). |
411 */ | 532 */ |
412 private void onCalledByNativeException(Exception e) { | 533 private void onCalledByNativeException(Exception e) { |
413 mSinkException = new IOException( | 534 mSinkException = new IOException( |
414 "CalledByNative method has thrown an exception", e); | 535 "CalledByNative method has thrown an exception", e); |
415 Log.e(ChromiumUrlRequestContext.LOG_TAG, | 536 Log.e(ChromiumUrlRequestContext.LOG_TAG, |
416 "Exception in CalledByNative method", e); | 537 "Exception in CalledByNative method", e); |
417 try { | 538 try { |
418 cancel(); | 539 cancel(); |
419 } catch (Exception cancel_exception) { | 540 } catch (Exception cancel_exception) { |
420 Log.e(ChromiumUrlRequestContext.LOG_TAG, | 541 Log.e(ChromiumUrlRequestContext.LOG_TAG, |
421 "Exception trying to cancel request", cancel_exception); | 542 "Exception trying to cancel request", cancel_exception); |
422 } | 543 } |
423 } | 544 } |
424 | 545 |
425 /** | 546 /** |
| 547 * A callback invoked when appending a chunk to the request has completed. |
| 548 */ |
| 549 @CalledByNative |
| 550 protected void onAppendChunkCompleted() { |
| 551 mAppendChunkSemaphore.release(); |
| 552 } |
| 553 |
| 554 /** |
426 * A callback invoked when the first chunk of the response has arrived. | 555 * A callback invoked when the first chunk of the response has arrived. |
427 */ | 556 */ |
428 @CalledByNative | 557 @CalledByNative |
429 private void onResponseStarted() { | 558 private void onResponseStarted() { |
430 try { | 559 try { |
431 mContentType = nativeGetContentType(mUrlRequestAdapter); | 560 mContentType = nativeGetContentType(mUrlRequestAdapter); |
432 mContentLength = nativeGetContentLength(mUrlRequestAdapter); | 561 mContentLength = nativeGetContentLength(mUrlRequestAdapter); |
433 mHeadersAvailable = true; | 562 mHeadersAvailable = true; |
434 | 563 |
435 if (mContentLengthLimit > 0 && | 564 if (mContentLengthLimit > 0 && |
(...skipping 162 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
598 String value); | 727 String value); |
599 | 728 |
600 private native void nativeSetMethod(long urlRequestAdapter, String method); | 729 private native void nativeSetMethod(long urlRequestAdapter, String method); |
601 | 730 |
602 private native void nativeSetUploadData(long urlRequestAdapter, | 731 private native void nativeSetUploadData(long urlRequestAdapter, |
603 String contentType, byte[] content); | 732 String contentType, byte[] content); |
604 | 733 |
605 private native void nativeSetUploadChannel(long urlRequestAdapter, | 734 private native void nativeSetUploadChannel(long urlRequestAdapter, |
606 String contentType, long contentLength); | 735 String contentType, long contentLength); |
607 | 736 |
| 737 private native void nativeEnableChunkedUpload(long urlRequestAdapter, |
| 738 String contentType); |
| 739 |
| 740 private native void nativeAppendChunkToUpload(long urlRequestAdapter, |
| 741 ByteBuffer chunk, int chunkSize, boolean isLastChunk); |
| 742 |
608 private native void nativeStart(long urlRequestAdapter); | 743 private native void nativeStart(long urlRequestAdapter); |
609 | 744 |
610 private native void nativeCancel(long urlRequestAdapter); | 745 private native void nativeCancel(long urlRequestAdapter); |
611 | 746 |
612 private native void nativeDestroyRequestAdapter(long urlRequestAdapter); | 747 private native void nativeDestroyRequestAdapter(long urlRequestAdapter); |
613 | 748 |
614 private native int nativeGetErrorCode(long urlRequestAdapter); | 749 private native int nativeGetErrorCode(long urlRequestAdapter); |
615 | 750 |
616 private native int nativeGetHttpStatusCode(long urlRequestAdapter); | 751 private native int nativeGetHttpStatusCode(long urlRequestAdapter); |
617 | 752 |
618 private native String nativeGetErrorString(long urlRequestAdapter); | 753 private native String nativeGetErrorString(long urlRequestAdapter); |
619 | 754 |
620 private native String nativeGetContentType(long urlRequestAdapter); | 755 private native String nativeGetContentType(long urlRequestAdapter); |
621 | 756 |
622 private native long nativeGetContentLength(long urlRequestAdapter); | 757 private native long nativeGetContentLength(long urlRequestAdapter); |
623 | 758 |
624 private native String nativeGetHeader(long urlRequestAdapter, String name); | 759 private native String nativeGetHeader(long urlRequestAdapter, String name); |
625 | 760 |
626 private native void nativeGetAllHeaders(long urlRequestAdapter, | 761 private native void nativeGetAllHeaders(long urlRequestAdapter, |
627 ResponseHeadersMap headers); | 762 ResponseHeadersMap headers); |
628 | 763 |
629 // Explicit class to work around JNI-generator generics confusion. | 764 // Explicit class to work around JNI-generator generics confusion. |
630 private class ResponseHeadersMap extends HashMap<String, List<String>> { | 765 private class ResponseHeadersMap extends HashMap<String, List<String>> { |
631 } | 766 } |
632 } | 767 } |
OLD | NEW |