Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(353)

Side by Side Diff: components/cronet/android/java/src/org/chromium/net/ChromiumUrlRequest.java

Issue 458633002: Merge UrlRequest.java into ChromiumUrlRequest.java (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Address review comments, reenable netLog test. Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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 org.apache.http.conn.ConnectTimeoutException;
8 import org.chromium.base.CalledByNative;
9 import org.chromium.base.JNINamespace;
10
7 import java.io.IOException; 11 import java.io.IOException;
12 import java.net.MalformedURLException;
13 import java.net.URL;
14 import java.net.UnknownHostException;
8 import java.nio.ByteBuffer; 15 import java.nio.ByteBuffer;
16 import java.nio.channels.ReadableByteChannel;
9 import java.nio.channels.WritableByteChannel; 17 import java.nio.channels.WritableByteChannel;
18 import java.util.ArrayList;
19 import java.util.HashMap;
20 import java.util.List;
10 import java.util.Map; 21 import java.util.Map;
22 import java.util.Map.Entry;
11 23
12 /** 24 /**
13 * Network request using the native http stack implementation. 25 * Network request using the native http stack implementation.
14 */ 26 */
15 public class ChromiumUrlRequest extends UrlRequest implements HttpUrlRequest { 27 @JNINamespace("cronet")
28 public class ChromiumUrlRequest implements HttpUrlRequest {
29 /**
30 * Native adapter object, owned by UrlRequest.
31 */
32 private long mUrlRequestAdapter;
33 private final ChromiumUrlRequestContext mRequestContext;
34 private final String mUrl;
35 private final int mPriority;
36 private final Map<String, String> mHeaders;
37 private final WritableByteChannel mSink;
38 private Map<String, String> mAdditionalHeaders;
39 private String mUploadContentType;
40 private String mMethod;
41 private byte[] mUploadData;
42 private ReadableByteChannel mUploadChannel;
43 private WritableByteChannel mOutputChannel;
44 private IOException mSinkException;
45 private volatile boolean mStarted;
46 private volatile boolean mCanceled;
47 private volatile boolean mRecycled;
48 private volatile boolean mFinished;
49 private boolean mHeadersAvailable;
50 private String mContentType;
51 private long mUploadContentLength;
52 private final HttpUrlRequestListener mListener;
53 private boolean mBufferFullResponse;
54 private long mOffset;
55 private long mContentLength;
56 private long mContentLengthLimit;
57 private boolean mCancelIfContentLengthOverLimit;
58 private boolean mContentLengthOverLimit;
59 private boolean mSkippingToOffset;
60 private long mSize;
61 private final Object mLock;
16 62
17 private final HttpUrlRequestListener mListener; 63 public ChromiumUrlRequest(ChromiumUrlRequestContext requestContext,
18
19 private boolean mBufferFullResponse;
20
21 private long mOffset;
22
23 private long mContentLength;
24
25 private long mContentLengthLimit;
26
27 private boolean mCancelIfContentLengthOverLimit;
28
29 private boolean mContentLengthOverLimit;
30
31 private boolean mSkippingToOffset;
32
33 private long mSize;
34
35 public ChromiumUrlRequest(UrlRequestContext requestContext,
36 String url, int priority, Map<String, String> headers, 64 String url, int priority, Map<String, String> headers,
37 HttpUrlRequestListener listener) { 65 HttpUrlRequestListener listener) {
38 this(requestContext, url, priority, headers, 66 this(requestContext, url, priority, headers,
39 new ChunkedWritableByteChannel(), listener); 67 new ChunkedWritableByteChannel(), listener);
40 mBufferFullResponse = true; 68 mBufferFullResponse = true;
41 } 69 }
42 70
43 public ChromiumUrlRequest(UrlRequestContext requestContext, 71 /**
72 * Constructor.
73 *
74 * @param requestContext The context.
75 * @param url The URL.
76 * @param priority Request priority, e.g. {@link #REQUEST_PRIORITY_MEDIUM}.
77 * @param headers HTTP headers.
78 * @param sink The output channel into which downloaded content will be
79 * written.
80 */
81 public ChromiumUrlRequest(ChromiumUrlRequestContext requestContext,
44 String url, int priority, Map<String, String> headers, 82 String url, int priority, Map<String, String> headers,
45 WritableByteChannel sink, HttpUrlRequestListener listener) { 83 WritableByteChannel sink, HttpUrlRequestListener listener) {
46 super(requestContext, url, convertRequestPriority(priority), headers, 84 if (requestContext == null) {
47 sink); 85 throw new NullPointerException("Context is required");
86 }
87 if (url == null) {
88 throw new NullPointerException("URL is required");
89 }
90 mRequestContext = requestContext;
91 mUrl = url;
92 mPriority = convertRequestPriority(priority);
93 mHeaders = headers;
94 mSink = sink;
95 mLock = new Object();
Charles 2014/08/14 18:23:50 Can we put this where the field is declared?
mef 2014/08/14 20:58:02 Done.
96 mUrlRequestAdapter = nativeCreateRequestAdapter(
97 mRequestContext.getChromiumUrlRequestContextAdapter(),
98 mUrl,
99 mPriority);
48 mListener = listener; 100 mListener = listener;
49 } 101 }
50 102
51 private static int convertRequestPriority(int priority) {
52 switch (priority) {
53 case HttpUrlRequest.REQUEST_PRIORITY_IDLE:
54 return UrlRequestPriority.IDLE;
55 case HttpUrlRequest.REQUEST_PRIORITY_LOWEST:
56 return UrlRequestPriority.LOWEST;
57 case HttpUrlRequest.REQUEST_PRIORITY_LOW:
58 return UrlRequestPriority.LOW;
59 case HttpUrlRequest.REQUEST_PRIORITY_MEDIUM:
60 return UrlRequestPriority.MEDIUM;
61 case HttpUrlRequest.REQUEST_PRIORITY_HIGHEST:
62 return UrlRequestPriority.HIGHEST;
63 default:
64 return UrlRequestPriority.MEDIUM;
65 }
66 }
67
68 @Override 103 @Override
69 public void setOffset(long offset) { 104 public void setOffset(long offset) {
70 mOffset = offset; 105 mOffset = offset;
71 if (offset != 0) { 106 if (offset != 0) {
72 addHeader("Range", "bytes=" + offset + "-"); 107 addHeader("Range", "bytes=" + offset + "-");
73 } 108 }
74 } 109 }
75 110
111 /**
112 * The compressed content length as reported by the server. May be -1 if
113 * the server did not provide a length. Some servers may also report the
114 * wrong number. Since this is the compressed content length, and only
115 * uncompressed content is returned by the consumer, the consumer should
116 * not rely on this value.
117 */
76 @Override 118 @Override
77 public long getContentLength() { 119 public long getContentLength() {
78 return mContentLength; 120 return mContentLength;
79 } 121 }
80 122
81 @Override 123 @Override
82 public void setContentLengthLimit(long limit, boolean cancelEarly) { 124 public void setContentLengthLimit(long limit, boolean cancelEarly) {
83 mContentLengthLimit = limit; 125 mContentLengthLimit = limit;
84 mCancelIfContentLengthOverLimit = cancelEarly; 126 mCancelIfContentLengthOverLimit = cancelEarly;
85 } 127 }
86 128
87 @Override 129 @Override
88 protected void onResponseStarted() { 130 public int getHttpStatusCode() {
89 super.onResponseStarted(); 131 int httpStatusCode = nativeGetHttpStatusCode(mUrlRequestAdapter);
90 132
91 mContentLength = super.getContentLength(); 133 // TODO(mef): Investigate the following:
134 // If we have been able to successfully resume a previously interrupted
135 // download,
136 // the status code will be 206, not 200. Since the rest of the
137 // application is
138 // expecting 200 to indicate success, we need to fake it.
139 if (httpStatusCode == 206) {
140 httpStatusCode = 200;
141 }
142 return httpStatusCode;
143 }
144
145 /**
146 * Returns an exception if any, or null if the request was completed
147 * successfully.
148 */
149 @Override
150 public IOException getException() {
151 if (mSinkException != null) {
152 return mSinkException;
153 }
154
155 validateNotRecycled();
156
157 int errorCode = nativeGetErrorCode(mUrlRequestAdapter);
158 switch (errorCode) {
159 case ChromiumUrlRequestError.SUCCESS:
160 if (mContentLengthOverLimit) {
161 return new ResponseTooLargeException();
162 }
163 return null;
164 case ChromiumUrlRequestError.UNKNOWN:
165 return new IOException(
166 nativeGetErrorString(mUrlRequestAdapter));
167 case ChromiumUrlRequestError.MALFORMED_URL:
168 return new MalformedURLException("Malformed URL: " + mUrl);
169 case ChromiumUrlRequestError.CONNECTION_TIMED_OUT:
170 return new ConnectTimeoutException("Connection timed out");
171 case ChromiumUrlRequestError.UNKNOWN_HOST:
172 String host;
173 try {
174 host = new URL(mUrl).getHost();
175 } catch (MalformedURLException e) {
176 host = mUrl;
177 }
178 return new UnknownHostException("Unknown host: " + host);
179 default:
180 throw new IllegalStateException(
181 "Unrecognized error code: " + errorCode);
182 }
183 }
184
185 @Override
186 public ByteBuffer getByteBuffer() {
187 return ((ChunkedWritableByteChannel)getSink()).getByteBuffer();
188 }
189
190 @Override
191 public byte[] getResponseAsBytes() {
192 return ((ChunkedWritableByteChannel)getSink()).getBytes();
193 }
194
195 /**
196 * Adds a request header. Must be done before request has started.
197 */
198 public void addHeader(String header, String value) {
199 synchronized (mLock) {
200 validateNotStarted();
201 if (mAdditionalHeaders == null) {
202 mAdditionalHeaders = new HashMap<String, String>();
203 }
204 mAdditionalHeaders.put(header, value);
205 }
206 }
207
208 /**
209 * Sets data to upload as part of a POST or PUT request.
210 *
211 * @param contentType MIME type of the upload content or null if this is not
212 * an upload.
213 * @param data The content that needs to be uploaded.
214 */
215 public void setUploadData(String contentType, byte[] data) {
216 synchronized (mLock) {
217 validateNotStarted();
218 mUploadContentType = contentType;
219 mUploadData = data;
220 mUploadChannel = null;
221 }
222 }
223
224 /**
225 * Sets a readable byte channel to upload as part of a POST or PUT request.
226 *
227 * @param contentType MIME type of the upload content or null if this is not
228 * an upload request.
229 * @param channel The channel to read to read upload data from if this is an
230 * upload request.
231 * @param contentLength The length of data to upload.
232 */
233 public void setUploadChannel(String contentType,
234 ReadableByteChannel channel, long contentLength) {
235 synchronized (mLock) {
236 validateNotStarted();
237 mUploadContentType = contentType;
238 mUploadChannel = channel;
239 mUploadContentLength = contentLength;
240 mUploadData = null;
241 }
242 }
243
244 /**
245 * Sets HTTP method for upload request. Only PUT or POST are allowed.
246 */
247 public void setHttpMethod(String method) {
248 validateNotStarted();
249 if (!("PUT".equals(method) || "POST".equals(method))) {
250 throw new IllegalArgumentException("Only PUT or POST are allowed.");
251 }
252 mMethod = method;
253 }
254
255 public WritableByteChannel getSink() {
256 return mSink;
257 }
258
259 public void start() {
260 synchronized (mLock) {
261 if (mCanceled) {
262 return;
263 }
264
265 validateNotStarted();
266 validateNotRecycled();
267
268 mStarted = true;
269
270 String method = mMethod;
271 if (method == null &&
272 ((mUploadData != null && mUploadData.length > 0) ||
273 mUploadChannel != null)) {
274 // Default to POST if there is data to upload but no method was
275 // specified.
276 method = "POST";
277 }
278
279 if (method != null) {
280 nativeSetMethod(mUrlRequestAdapter, method);
281 }
282
283 if (mHeaders != null && !mHeaders.isEmpty()) {
284 for (Entry<String, String> entry : mHeaders.entrySet()) {
285 nativeAddHeader(mUrlRequestAdapter, entry.getKey(),
286 entry.getValue());
287 }
288 }
289
290 if (mAdditionalHeaders != null) {
291 for (Entry<String, String> entry :
292 mAdditionalHeaders.entrySet()) {
293 nativeAddHeader(mUrlRequestAdapter, entry.getKey(),
294 entry.getValue());
295 }
296 }
297
298 if (mUploadData != null && mUploadData.length > 0) {
299 nativeSetUploadData(mUrlRequestAdapter, mUploadContentType,
300 mUploadData);
301 } else if (mUploadChannel != null) {
302 nativeSetUploadChannel(mUrlRequestAdapter, mUploadContentType,
303 mUploadContentLength);
304 }
305
306 nativeStart(mUrlRequestAdapter);
307 }
308 }
309
310 public void cancel() {
311 synchronized (mLock) {
312 if (mCanceled) {
313 return;
314 }
315
316 mCanceled = true;
317
318 if (!mRecycled) {
319 nativeCancel(mUrlRequestAdapter);
320 }
321 }
322 }
323
324 public boolean isCanceled() {
325 synchronized (mLock) {
326 return mCanceled;
327 }
328 }
329
330 public boolean isRecycled() {
331 synchronized (mLock) {
332 return mRecycled;
333 }
334 }
335
336 public String getContentType() {
337 return mContentType;
338 }
339
340 public String getHeader(String name) {
341 validateHeadersAvailable();
342 return nativeGetHeader(mUrlRequestAdapter, name);
343 }
344
345 // All response headers.
346 public Map<String, List<String>> getAllHeaders() {
347 validateHeadersAvailable();
348 ResponseHeadersMap result = new ResponseHeadersMap();
349 nativeGetAllHeaders(mUrlRequestAdapter, result);
350 return result;
351 }
352
353 public String getUrl() {
354 return mUrl;
355 }
356
357 private static int convertRequestPriority(int priority) {
358 switch (priority) {
359 case HttpUrlRequest.REQUEST_PRIORITY_IDLE:
360 return ChromiumUrlRequestPriority.IDLE;
361 case HttpUrlRequest.REQUEST_PRIORITY_LOWEST:
362 return ChromiumUrlRequestPriority.LOWEST;
363 case HttpUrlRequest.REQUEST_PRIORITY_LOW:
364 return ChromiumUrlRequestPriority.LOW;
365 case HttpUrlRequest.REQUEST_PRIORITY_MEDIUM:
366 return ChromiumUrlRequestPriority.MEDIUM;
367 case HttpUrlRequest.REQUEST_PRIORITY_HIGHEST:
368 return ChromiumUrlRequestPriority.HIGHEST;
369 default:
370 return ChromiumUrlRequestPriority.MEDIUM;
371 }
372 }
373
374 private void onContentLengthOverLimit() {
375 mContentLengthOverLimit = true;
376 cancel();
377 }
378
379 /**
380 * A callback invoked when the response has been fully consumed.
381 */
382 private void onRequestComplete() {
383 mListener.onRequestComplete(this);
384 }
385
386 private void validateNotRecycled() {
387 if (mRecycled) {
388 throw new IllegalStateException("Accessing recycled request");
389 }
390 }
391
392 private void validateNotStarted() {
393 if (mStarted) {
394 throw new IllegalStateException("Request already started");
395 }
396 }
397
398 private void validateHeadersAvailable() {
399 if (!mHeadersAvailable) {
400 throw new IllegalStateException("Response headers not available");
401 }
402 }
403
404 // Private methods called by native library.
405
406 /**
407 * A callback invoked when the first chunk of the response has arrived.
408 */
409 @CalledByNative
410 private void onResponseStarted() {
411 mContentType = nativeGetContentType(mUrlRequestAdapter);
412 mContentLength = nativeGetContentLength(mUrlRequestAdapter);
413 mHeadersAvailable = true;
414
92 if (mContentLengthLimit > 0 && mContentLength > mContentLengthLimit 415 if (mContentLengthLimit > 0 && mContentLength > mContentLengthLimit
93 && mCancelIfContentLengthOverLimit) { 416 && mCancelIfContentLengthOverLimit) {
94 onContentLengthOverLimit(); 417 onContentLengthOverLimit();
95 return; 418 return;
96 } 419 }
97 420
98 if (mBufferFullResponse && mContentLength != -1 421 if (mBufferFullResponse && mContentLength != -1
99 && !mContentLengthOverLimit) { 422 && !mContentLengthOverLimit) {
100 ((ChunkedWritableByteChannel)getSink()).setCapacity( 423 ((ChunkedWritableByteChannel)getSink()).setCapacity(
101 (int)mContentLength); 424 (int)mContentLength);
102 } 425 }
103 426
104 if (mOffset != 0) { 427 if (mOffset != 0) {
105 // The server may ignore the request for a byte range. 428 // The server may ignore the request for a byte range.
106 if (super.getHttpStatusCode() == 200) { 429 if (getHttpStatusCode() == 200) {
430 // TODO(mef): Revisit this logic.
107 if (mContentLength != -1) { 431 if (mContentLength != -1) {
108 mContentLength -= mOffset; 432 mContentLength -= mOffset;
109 } 433 }
110 mSkippingToOffset = true; 434 mSkippingToOffset = true;
111 } else { 435 } else {
112 mSize = mOffset; 436 mSize = mOffset;
113 } 437 }
114 } 438 }
115 mListener.onResponseStarted(this); 439 mListener.onResponseStarted(this);
116 } 440 }
117 441
118 @Override 442 /**
119 protected void onBytesRead(ByteBuffer buffer) { 443 * Consumes a portion of the response.
444 *
445 * @param byteBuffer The ByteBuffer to append. Must be a direct buffer, and
446 * no references to it may be retained after the method ends, as
447 * it wraps code managed on the native heap.
448 */
449 @CalledByNative
450 private void onBytesRead(ByteBuffer buffer) {
120 if (mContentLengthOverLimit) { 451 if (mContentLengthOverLimit) {
121 return; 452 return;
122 } 453 }
123 454
124 int size = buffer.remaining(); 455 int size = buffer.remaining();
125 mSize += size; 456 mSize += size;
126 if (mSkippingToOffset) { 457 if (mSkippingToOffset) {
127 if (mSize <= mOffset) { 458 if (mSize <= mOffset) {
128 return; 459 return;
129 } else { 460 } else {
130 mSkippingToOffset = false; 461 mSkippingToOffset = false;
131 buffer.position((int)(mOffset - (mSize - size))); 462 buffer.position((int)(mOffset - (mSize - size)));
132 } 463 }
133 } 464 }
134 465
135 if (mContentLengthLimit != 0 && mSize > mContentLengthLimit) { 466 boolean contentLengthOverLimit =
467 (mContentLengthLimit != 0 && mSize > mContentLengthLimit);
468 if (contentLengthOverLimit) {
136 buffer.limit(size - (int)(mSize - mContentLengthLimit)); 469 buffer.limit(size - (int)(mSize - mContentLengthLimit));
137 super.onBytesRead(buffer);
138 onContentLengthOverLimit();
139 return;
140 } 470 }
141 471
142 super.onBytesRead(buffer); 472 try {
473 while (buffer.hasRemaining()) {
474 mSink.write(buffer);
475 }
476 } catch (IOException e) {
477 mSinkException = e;
478 cancel();
479 }
480 if (contentLengthOverLimit) {
481 onContentLengthOverLimit();
482 }
143 } 483 }
144 484
145 private void onContentLengthOverLimit() { 485 /**
146 mContentLengthOverLimit = true; 486 * Notifies the listener, releases native data structures.
147 cancel(); 487 */
488 @SuppressWarnings("unused")
489 @CalledByNative
490 private void finish() {
491 synchronized (mLock) {
492 mFinished = true;
493
494 if (mRecycled) {
495 return;
496 }
497 try {
498 mSink.close();
499 } catch (IOException e) {
500 // Ignore
501 }
502 onRequestComplete();
503 nativeDestroyRequestAdapter(mUrlRequestAdapter);
504 mUrlRequestAdapter = 0;
505 mRecycled = true;
506 }
148 } 507 }
149 508
150 @Override 509 /**
151 protected void onRequestComplete() { 510 * Appends header |name| with value |value| to |headersMap|.
152 mListener.onRequestComplete(this); 511 */
512 @SuppressWarnings("unused")
513 @CalledByNative
514 private void onAppendResponseHeader(ResponseHeadersMap headersMap,
515 String name, String value) {
516 if (!headersMap.containsKey(name)) {
517 headersMap.put(name, new ArrayList<String>());
518 }
519 headersMap.get(name).add(value);
153 } 520 }
154 521
155 @Override 522 /**
156 public int getHttpStatusCode() { 523 * Reads a sequence of bytes from upload channel into the given buffer.
157 int httpStatusCode = super.getHttpStatusCode(); 524 * @param dest The buffer into which bytes are to be transferred.
158 525 * @return Returns number of bytes read (could be 0) or -1 and closes
159 // TODO(mef): Investigate the following: 526 * the channel if error occured.
160 // If we have been able to successfully resume a previously interrupted 527 */
161 // download, 528 @SuppressWarnings("unused")
162 // the status code will be 206, not 200. Since the rest of the 529 @CalledByNative
163 // application is 530 private int readFromUploadChannel(ByteBuffer dest) {
164 // expecting 200 to indicate success, we need to fake it. 531 if (mUploadChannel == null || !mUploadChannel.isOpen())
165 if (httpStatusCode == 206) { 532 return -1;
166 httpStatusCode = 200; 533 try {
534 int result = mUploadChannel.read(dest);
535 if (result < 0) {
536 mUploadChannel.close();
537 return 0;
538 }
539 return result;
540 } catch (IOException e) {
541 mSinkException = e;
542 try {
543 mUploadChannel.close();
544 } catch (IOException ignored) {
545 // Ignore this exception.
546 }
547 cancel();
548 return -1;
167 } 549 }
168 return httpStatusCode;
169 } 550 }
170 551
171 @Override 552 // Native methods are implemented in chromium_url_request.cc.
172 public IOException getException() {
173 IOException ex = super.getException();
174 if (ex == null && mContentLengthOverLimit) {
175 ex = new ResponseTooLargeException();
176 }
177 return ex;
178 }
179 553
180 @Override 554 private native long nativeCreateRequestAdapter(
181 public ByteBuffer getByteBuffer() { 555 long ChromiumUrlRequestContextAdapter, String url, int priority);
182 return ((ChunkedWritableByteChannel)getSink()).getByteBuffer();
183 }
184 556
185 @Override 557 private native void nativeAddHeader(long urlRequestAdapter, String name,
186 public byte[] getResponseAsBytes() { 558 String value);
187 return ((ChunkedWritableByteChannel)getSink()).getBytes(); 559
560 private native void nativeSetMethod(long urlRequestAdapter, String method);
561
562 private native void nativeSetUploadData(long urlRequestAdapter,
563 String contentType, byte[] content);
564
565 private native void nativeSetUploadChannel(long urlRequestAdapter,
566 String contentType, long contentLength);
567
568 private native void nativeStart(long urlRequestAdapter);
569
570 private native void nativeCancel(long urlRequestAdapter);
571
572 private native void nativeDestroyRequestAdapter(long urlRequestAdapter);
573
574 private native int nativeGetErrorCode(long urlRequestAdapter);
575
576 private native int nativeGetHttpStatusCode(long urlRequestAdapter);
577
578 private native String nativeGetErrorString(long urlRequestAdapter);
579
580 private native String nativeGetContentType(long urlRequestAdapter);
581
582 private native long nativeGetContentLength(long urlRequestAdapter);
583
584 private native String nativeGetHeader(long urlRequestAdapter, String name);
585
586 private native void nativeGetAllHeaders(long urlRequestAdapter,
587 ResponseHeadersMap headers);
588
589 // Explicit class to work around JNI-generator generics confusion.
590 private class ResponseHeadersMap extends HashMap<String, List<String>> {
188 } 591 }
189 } 592 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698