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; |
(...skipping 29 matching lines...) Expand all Loading... |
40 private final WritableByteChannel mSink; | 40 private final WritableByteChannel mSink; |
41 private Map<String, String> mAdditionalHeaders; | 41 private Map<String, String> mAdditionalHeaders; |
42 private String mUploadContentType; | 42 private String mUploadContentType; |
43 private String mMethod; | 43 private String mMethod; |
44 private byte[] mUploadData; | 44 private byte[] mUploadData; |
45 private ReadableByteChannel mUploadChannel; | 45 private ReadableByteChannel mUploadChannel; |
46 private boolean mChunkedUpload; | 46 private boolean mChunkedUpload; |
47 private IOException mSinkException; | 47 private IOException mSinkException; |
48 private volatile boolean mStarted; | 48 private volatile boolean mStarted; |
49 private volatile boolean mCanceled; | 49 private volatile boolean mCanceled; |
50 private volatile boolean mRecycled; | |
51 private volatile boolean mFinished; | 50 private volatile boolean mFinished; |
52 private boolean mHeadersAvailable; | 51 private boolean mHeadersAvailable; |
53 private String mContentType; | |
54 private long mUploadContentLength; | 52 private long mUploadContentLength; |
55 private final HttpUrlRequestListener mListener; | 53 private final HttpUrlRequestListener mListener; |
56 private boolean mBufferFullResponse; | 54 private boolean mBufferFullResponse; |
57 private long mOffset; | 55 private long mOffset; |
58 private long mContentLength; | |
59 private long mContentLengthLimit; | 56 private long mContentLengthLimit; |
60 private boolean mCancelIfContentLengthOverLimit; | 57 private boolean mCancelIfContentLengthOverLimit; |
61 private boolean mContentLengthOverLimit; | 58 private boolean mContentLengthOverLimit; |
62 private boolean mSkippingToOffset; | 59 private boolean mSkippingToOffset; |
63 private long mSize; | 60 private long mSize; |
| 61 |
64 // Indicates whether redirects have been disabled. | 62 // Indicates whether redirects have been disabled. |
65 private boolean mDisableRedirects; | 63 private boolean mDisableRedirects; |
| 64 |
| 65 // Http status code. Default to 0. Populated in onResponseStarted(). |
| 66 private int mHttpStatusCode = 0; |
| 67 |
| 68 // Http status text. Default to null. Populated in onResponseStarted(). |
| 69 private String mHttpStatusText; |
| 70 |
| 71 // Content type. Default to null. Populated in onResponseStarted(). |
| 72 private String mContentType; |
| 73 |
| 74 // Compressed content length as reported by the server. Populated in onRespo
nseStarted(). |
| 75 private long mContentLength; |
| 76 |
| 77 // Native error code. Default to no error. Populated in onRequestComplete(). |
| 78 private int mErrorCode = ChromiumUrlRequestError.SUCCESS; |
| 79 |
| 80 // Native error string. Default to null. Populated in onRequestComplete(). |
| 81 private String mErrorString; |
| 82 |
| 83 // Protects access of mUrlRequestAdapter, mStarted, mCanceled, and mFinished
. |
66 private final Object mLock = new Object(); | 84 private final Object mLock = new Object(); |
67 | 85 |
68 public ChromiumUrlRequest(ChromiumUrlRequestContext requestContext, | 86 public ChromiumUrlRequest(ChromiumUrlRequestContext requestContext, |
69 String url, int priority, Map<String, String> headers, | 87 String url, int priority, Map<String, String> headers, |
70 HttpUrlRequestListener listener) { | 88 HttpUrlRequestListener listener) { |
71 this(requestContext, url, priority, headers, | 89 this(requestContext, url, priority, headers, |
72 new ChunkedWritableByteChannel(), listener); | 90 new ChunkedWritableByteChannel(), listener); |
73 mBufferFullResponse = true; | 91 mBufferFullResponse = true; |
74 } | 92 } |
75 | 93 |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
125 } | 143 } |
126 | 144 |
127 @Override | 145 @Override |
128 public void setContentLengthLimit(long limit, boolean cancelEarly) { | 146 public void setContentLengthLimit(long limit, boolean cancelEarly) { |
129 mContentLengthLimit = limit; | 147 mContentLengthLimit = limit; |
130 mCancelIfContentLengthOverLimit = cancelEarly; | 148 mCancelIfContentLengthOverLimit = cancelEarly; |
131 } | 149 } |
132 | 150 |
133 @Override | 151 @Override |
134 public int getHttpStatusCode() { | 152 public int getHttpStatusCode() { |
135 int httpStatusCode = nativeGetHttpStatusCode(mUrlRequestAdapter); | |
136 | |
137 // TODO(mef): Investigate the following: | 153 // TODO(mef): Investigate the following: |
138 // If we have been able to successfully resume a previously interrupted | 154 // If we have been able to successfully resume a previously interrupted |
139 // download, the status code will be 206, not 200. Since the rest of the | 155 // download, the status code will be 206, not 200. Since the rest of the |
140 // application is expecting 200 to indicate success, we need to fake it. | 156 // application is expecting 200 to indicate success, we need to fake it. |
141 if (httpStatusCode == 206) { | 157 if (mHttpStatusCode == 206) { |
142 httpStatusCode = 200; | 158 return 200; |
143 } | 159 } |
144 return httpStatusCode; | 160 return mHttpStatusCode; |
145 } | 161 } |
146 | 162 |
147 @Override | 163 @Override |
148 public String getHttpStatusText() { | 164 public String getHttpStatusText() { |
149 return nativeGetHttpStatusText(mUrlRequestAdapter); | 165 return mHttpStatusText; |
150 } | 166 } |
151 | 167 |
152 /** | 168 /** |
153 * Returns an exception if any, or null if the request was completed | 169 * Returns an exception if any, or null if the request has not completed or |
154 * successfully. | 170 * completed successfully. |
155 */ | 171 */ |
156 @Override | 172 @Override |
157 public IOException getException() { | 173 public IOException getException() { |
158 if (mSinkException != null) { | 174 if (mSinkException != null) { |
159 return mSinkException; | 175 return mSinkException; |
160 } | 176 } |
161 | 177 |
162 validateNotRecycled(); | 178 switch (mErrorCode) { |
163 | |
164 int errorCode = nativeGetErrorCode(mUrlRequestAdapter); | |
165 switch (errorCode) { | |
166 case ChromiumUrlRequestError.SUCCESS: | 179 case ChromiumUrlRequestError.SUCCESS: |
167 if (mContentLengthOverLimit) { | 180 if (mContentLengthOverLimit) { |
168 return new ResponseTooLargeException(); | 181 return new ResponseTooLargeException(); |
169 } | 182 } |
170 return null; | 183 return null; |
171 case ChromiumUrlRequestError.UNKNOWN: | 184 case ChromiumUrlRequestError.UNKNOWN: |
172 return new IOException( | 185 return new IOException(mErrorString); |
173 nativeGetErrorString(mUrlRequestAdapter)); | |
174 case ChromiumUrlRequestError.MALFORMED_URL: | 186 case ChromiumUrlRequestError.MALFORMED_URL: |
175 return new MalformedURLException("Malformed URL: " + mUrl); | 187 return new MalformedURLException("Malformed URL: " + mUrl); |
176 case ChromiumUrlRequestError.CONNECTION_TIMED_OUT: | 188 case ChromiumUrlRequestError.CONNECTION_TIMED_OUT: |
177 return new ConnectTimeoutException("Connection timed out"); | 189 return new ConnectTimeoutException("Connection timed out"); |
178 case ChromiumUrlRequestError.UNKNOWN_HOST: | 190 case ChromiumUrlRequestError.UNKNOWN_HOST: |
179 String host; | 191 String host; |
180 try { | 192 try { |
181 host = new URL(mUrl).getHost(); | 193 host = new URL(mUrl).getHost(); |
182 } catch (MalformedURLException e) { | 194 } catch (MalformedURLException e) { |
183 host = mUrl; | 195 host = mUrl; |
184 } | 196 } |
185 return new UnknownHostException("Unknown host: " + host); | 197 return new UnknownHostException("Unknown host: " + host); |
186 case ChromiumUrlRequestError.TOO_MANY_REDIRECTS: | 198 case ChromiumUrlRequestError.TOO_MANY_REDIRECTS: |
187 return new IOException("Request failed because there were too " | 199 return new IOException("Request failed because there were too " |
188 + "many redirects or redirects have been disabled"); | 200 + "many redirects or redirects have been disabled"); |
189 default: | 201 default: |
190 throw new IllegalStateException( | 202 throw new IllegalStateException( |
191 "Unrecognized error code: " + errorCode); | 203 "Unrecognized error code: " + mErrorCode); |
192 } | 204 } |
193 } | 205 } |
194 | 206 |
195 @Override | 207 @Override |
196 public ByteBuffer getByteBuffer() { | 208 public ByteBuffer getByteBuffer() { |
197 return ((ChunkedWritableByteChannel) getSink()).getByteBuffer(); | 209 return ((ChunkedWritableByteChannel) getSink()).getByteBuffer(); |
198 } | 210 } |
199 | 211 |
200 @Override | 212 @Override |
201 public byte[] getResponseAsBytes() { | 213 public byte[] getResponseAsBytes() { |
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
310 } | 322 } |
311 | 323 |
312 @Override | 324 @Override |
313 public void setHttpMethod(String method) { | 325 public void setHttpMethod(String method) { |
314 validateNotStarted(); | 326 validateNotStarted(); |
315 mMethod = method; | 327 mMethod = method; |
316 } | 328 } |
317 | 329 |
318 @Override | 330 @Override |
319 public void disableRedirects() { | 331 public void disableRedirects() { |
320 mDisableRedirects = true; | 332 synchronized (mLock) { |
321 validateNotStarted(); | 333 validateNotStarted(); |
322 nativeDisableRedirects(mUrlRequestAdapter); | 334 validateNativeAdapterNotDestroyed(); |
| 335 mDisableRedirects = true; |
| 336 nativeDisableRedirects(mUrlRequestAdapter); |
| 337 } |
323 } | 338 } |
324 | 339 |
325 public WritableByteChannel getSink() { | 340 public WritableByteChannel getSink() { |
326 return mSink; | 341 return mSink; |
327 } | 342 } |
328 | 343 |
329 @Override | 344 @Override |
330 public void start() { | 345 public void start() { |
331 synchronized (mLock) { | 346 synchronized (mLock) { |
332 if (mCanceled) { | 347 if (mCanceled) { |
333 return; | 348 return; |
334 } | 349 } |
335 | 350 |
336 validateNotStarted(); | 351 validateNotStarted(); |
337 validateNotRecycled(); | 352 validateNativeAdapterNotDestroyed(); |
338 | 353 |
339 mStarted = true; | 354 mStarted = true; |
340 | 355 |
341 if (mHeaders != null && !mHeaders.isEmpty()) { | 356 if (mHeaders != null && !mHeaders.isEmpty()) { |
342 for (Entry<String, String> entry : mHeaders.entrySet()) { | 357 for (Entry<String, String> entry : mHeaders.entrySet()) { |
343 nativeAddHeader(mUrlRequestAdapter, entry.getKey(), | 358 nativeAddHeader(mUrlRequestAdapter, entry.getKey(), |
344 entry.getValue()); | 359 entry.getValue()); |
345 } | 360 } |
346 } | 361 } |
347 | 362 |
(...skipping 29 matching lines...) Expand all Loading... |
377 | 392 |
378 @Override | 393 @Override |
379 public void cancel() { | 394 public void cancel() { |
380 synchronized (mLock) { | 395 synchronized (mLock) { |
381 if (mCanceled) { | 396 if (mCanceled) { |
382 return; | 397 return; |
383 } | 398 } |
384 | 399 |
385 mCanceled = true; | 400 mCanceled = true; |
386 | 401 |
387 if (!mRecycled) { | 402 if (mUrlRequestAdapter != 0) { |
388 nativeCancel(mUrlRequestAdapter); | 403 nativeCancel(mUrlRequestAdapter); |
389 } | 404 } |
390 } | 405 } |
391 } | 406 } |
392 | 407 |
393 @Override | 408 @Override |
394 public boolean isCanceled() { | 409 public boolean isCanceled() { |
395 synchronized (mLock) { | 410 synchronized (mLock) { |
396 return mCanceled; | 411 return mCanceled; |
397 } | 412 } |
398 } | 413 } |
399 | 414 |
400 public boolean isRecycled() { | |
401 synchronized (mLock) { | |
402 return mRecycled; | |
403 } | |
404 } | |
405 | |
406 @Override | 415 @Override |
407 public String getNegotiatedProtocol() { | 416 public String getNegotiatedProtocol() { |
408 validateNotRecycled(); | 417 synchronized (mLock) { |
409 validateHeadersAvailable(); | 418 validateNativeAdapterNotDestroyed(); |
410 return nativeGetNegotiatedProtocol(mUrlRequestAdapter); | 419 validateHeadersAvailable(); |
| 420 return nativeGetNegotiatedProtocol(mUrlRequestAdapter); |
| 421 } |
411 } | 422 } |
412 | 423 |
413 @Override | 424 @Override |
414 public String getContentType() { | 425 public String getContentType() { |
415 return mContentType; | 426 return mContentType; |
416 } | 427 } |
417 | 428 |
418 @Override | 429 @Override |
419 public String getHeader(String name) { | 430 public String getHeader(String name) { |
420 validateNotRecycled(); | 431 synchronized (mLock) { |
421 validateHeadersAvailable(); | 432 validateNativeAdapterNotDestroyed(); |
422 return nativeGetHeader(mUrlRequestAdapter, name); | 433 validateHeadersAvailable(); |
| 434 return nativeGetHeader(mUrlRequestAdapter, name); |
| 435 } |
423 } | 436 } |
424 | 437 |
425 // All response headers. | 438 // All response headers. |
426 @Override | 439 @Override |
427 public Map<String, List<String>> getAllHeaders() { | 440 public Map<String, List<String>> getAllHeaders() { |
428 validateNotRecycled(); | 441 synchronized (mLock) { |
429 validateHeadersAvailable(); | 442 validateNativeAdapterNotDestroyed(); |
430 ResponseHeadersMap result = new ResponseHeadersMap(); | 443 validateHeadersAvailable(); |
431 nativeGetAllHeaders(mUrlRequestAdapter, result); | 444 ResponseHeadersMap result = new ResponseHeadersMap(); |
432 return result; | 445 nativeGetAllHeaders(mUrlRequestAdapter, result); |
| 446 return result; |
| 447 } |
433 } | 448 } |
434 | 449 |
435 @Override | 450 @Override |
436 public String getUrl() { | 451 public String getUrl() { |
437 return mUrl; | 452 return mUrl; |
438 } | 453 } |
439 | 454 |
440 private static int convertRequestPriority(int priority) { | 455 private static int convertRequestPriority(int priority) { |
441 switch (priority) { | 456 switch (priority) { |
442 case HttpUrlRequest.REQUEST_PRIORITY_IDLE: | 457 case HttpUrlRequest.REQUEST_PRIORITY_IDLE: |
(...skipping 13 matching lines...) Expand all Loading... |
456 | 471 |
457 private void onContentLengthOverLimit() { | 472 private void onContentLengthOverLimit() { |
458 mContentLengthOverLimit = true; | 473 mContentLengthOverLimit = true; |
459 cancel(); | 474 cancel(); |
460 } | 475 } |
461 | 476 |
462 /** | 477 /** |
463 * A callback invoked when the response has been fully consumed. | 478 * A callback invoked when the response has been fully consumed. |
464 */ | 479 */ |
465 private void onRequestComplete() { | 480 private void onRequestComplete() { |
| 481 mErrorCode = nativeGetErrorCode(mUrlRequestAdapter); |
| 482 mErrorString = nativeGetErrorString(mUrlRequestAdapter); |
| 483 // When there is an error or redirects have been disabled, |
| 484 // onResponseStarted is often not invoked. |
| 485 // Populate status code and status text if that's the case. |
| 486 // Note that besides redirects, these two fields may be set on the |
| 487 // request for AUTH and CERT requests. |
| 488 if (mErrorCode != ChromiumUrlRequestError.SUCCESS) { |
| 489 mHttpStatusCode = nativeGetHttpStatusCode(mUrlRequestAdapter); |
| 490 mHttpStatusText = nativeGetHttpStatusText(mUrlRequestAdapter); |
| 491 } |
466 mListener.onRequestComplete(this); | 492 mListener.onRequestComplete(this); |
467 } | 493 } |
468 | 494 |
469 private void validateNotRecycled() { | 495 private void validateNativeAdapterNotDestroyed() { |
470 if (mRecycled) { | 496 if (mUrlRequestAdapter == 0) { |
471 throw new IllegalStateException("Accessing recycled request"); | 497 throw new IllegalStateException("Adapter has been destroyed"); |
472 } | 498 } |
473 } | 499 } |
474 | 500 |
475 private void validateNotStarted() { | 501 private void validateNotStarted() { |
476 if (mStarted) { | 502 if (mStarted) { |
477 throw new IllegalStateException("Request already started"); | 503 throw new IllegalStateException("Request already started"); |
478 } | 504 } |
479 } | 505 } |
480 | 506 |
481 private void validateHeadersAvailable() { | 507 private void validateHeadersAvailable() { |
(...skipping 26 matching lines...) Expand all Loading... |
508 "Exception trying to cancel request", cancel_exception); | 534 "Exception trying to cancel request", cancel_exception); |
509 } | 535 } |
510 } | 536 } |
511 | 537 |
512 /** | 538 /** |
513 * A callback invoked when the first chunk of the response has arrived. | 539 * A callback invoked when the first chunk of the response has arrived. |
514 */ | 540 */ |
515 @CalledByNative | 541 @CalledByNative |
516 private void onResponseStarted() { | 542 private void onResponseStarted() { |
517 try { | 543 try { |
| 544 mHttpStatusCode = nativeGetHttpStatusCode(mUrlRequestAdapter); |
| 545 mHttpStatusText = nativeGetHttpStatusText(mUrlRequestAdapter); |
518 mContentType = nativeGetContentType(mUrlRequestAdapter); | 546 mContentType = nativeGetContentType(mUrlRequestAdapter); |
519 mContentLength = nativeGetContentLength(mUrlRequestAdapter); | 547 mContentLength = nativeGetContentLength(mUrlRequestAdapter); |
520 mHeadersAvailable = true; | 548 mHeadersAvailable = true; |
521 | 549 |
522 if (mContentLengthLimit > 0 | 550 if (mContentLengthLimit > 0 |
523 && mContentLength > mContentLengthLimit | 551 && mContentLength > mContentLengthLimit |
524 && mCancelIfContentLengthOverLimit) { | 552 && mCancelIfContentLengthOverLimit) { |
525 onContentLengthOverLimit(); | 553 onContentLengthOverLimit(); |
526 return; | 554 return; |
527 } | 555 } |
528 | 556 |
529 if (mBufferFullResponse && mContentLength != -1 | 557 if (mBufferFullResponse && mContentLength != -1 |
530 && !mContentLengthOverLimit) { | 558 && !mContentLengthOverLimit) { |
531 ((ChunkedWritableByteChannel) getSink()).setCapacity( | 559 ((ChunkedWritableByteChannel) getSink()).setCapacity( |
532 (int) mContentLength); | 560 (int) mContentLength); |
533 } | 561 } |
534 | 562 |
535 if (mOffset != 0) { | 563 if (mOffset != 0) { |
536 // The server may ignore the request for a byte range, in which | 564 // The server may ignore the request for a byte range, in which |
537 // case status code will be 200, instead of 206. Note that we | 565 // case status code will be 200, instead of 206. Note that we |
538 // cannot call getHttpStatusCode as it rewrites 206 into 200. | 566 // cannot call getHttpStatusCode as it rewrites 206 into 200. |
539 if (nativeGetHttpStatusCode(mUrlRequestAdapter) == 200) { | 567 if (mHttpStatusCode == 200) { |
540 // TODO(mef): Revisit this logic. | 568 // TODO(mef): Revisit this logic. |
541 if (mContentLength != -1) { | 569 if (mContentLength != -1) { |
542 mContentLength -= mOffset; | 570 mContentLength -= mOffset; |
543 } | 571 } |
544 mSkippingToOffset = true; | 572 mSkippingToOffset = true; |
545 } else { | 573 } else { |
546 mSize = mOffset; | 574 mSize = mOffset; |
547 } | 575 } |
548 } | 576 } |
549 mListener.onResponseStarted(this); | 577 mListener.onResponseStarted(this); |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
600 @SuppressWarnings("unused") | 628 @SuppressWarnings("unused") |
601 @CalledByNative | 629 @CalledByNative |
602 private void finish() { | 630 private void finish() { |
603 try { | 631 try { |
604 synchronized (mLock) { | 632 synchronized (mLock) { |
605 if (mDisableRedirects) { | 633 if (mDisableRedirects) { |
606 mHeadersAvailable = true; | 634 mHeadersAvailable = true; |
607 } | 635 } |
608 mFinished = true; | 636 mFinished = true; |
609 | 637 |
610 if (mRecycled) { | 638 if (mUrlRequestAdapter == 0) { |
611 return; | 639 return; |
612 } | 640 } |
613 try { | 641 try { |
614 mSink.close(); | 642 mSink.close(); |
615 } catch (IOException e) { | 643 } catch (IOException e) { |
616 // Ignore | 644 // Ignore |
617 } | 645 } |
618 try { | 646 try { |
619 if (mUploadChannel != null && mUploadChannel.isOpen()) { | 647 if (mUploadChannel != null && mUploadChannel.isOpen()) { |
620 mUploadChannel.close(); | 648 mUploadChannel.close(); |
621 } | 649 } |
622 } catch (IOException e) { | 650 } catch (IOException e) { |
623 // Ignore | 651 // Ignore |
624 } | 652 } |
625 onRequestComplete(); | 653 onRequestComplete(); |
626 nativeDestroyRequestAdapter(mUrlRequestAdapter); | 654 nativeDestroyRequestAdapter(mUrlRequestAdapter); |
627 mUrlRequestAdapter = 0; | 655 mUrlRequestAdapter = 0; |
628 mRecycled = true; | |
629 } | 656 } |
630 } catch (Exception e) { | 657 } catch (Exception e) { |
631 mSinkException = new IOException("Exception in finish", e); | 658 mSinkException = new IOException("Exception in finish", e); |
632 } | 659 } |
633 } | 660 } |
634 | 661 |
635 /** | 662 /** |
636 * Appends header |name| with value |value| to |headersMap|. | 663 * Appends header |name| with value |value| to |headersMap|. |
637 */ | 664 */ |
638 @SuppressWarnings("unused") | 665 @SuppressWarnings("unused") |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
720 private native void nativeGetAllHeaders(long urlRequestAdapter, | 747 private native void nativeGetAllHeaders(long urlRequestAdapter, |
721 ResponseHeadersMap headers); | 748 ResponseHeadersMap headers); |
722 | 749 |
723 private native String nativeGetNegotiatedProtocol(long urlRequestAdapter); | 750 private native String nativeGetNegotiatedProtocol(long urlRequestAdapter); |
724 | 751 |
725 // Explicit class to work around JNI-generator generics confusion. | 752 // Explicit class to work around JNI-generator generics confusion. |
726 private static class ResponseHeadersMap extends | 753 private static class ResponseHeadersMap extends |
727 HashMap<String, List<String>> { | 754 HashMap<String, List<String>> { |
728 } | 755 } |
729 } | 756 } |
OLD | NEW |