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

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

Powered by Google App Engine
This is Rietveld 408576698