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

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

Issue 586143002: Initial implementation of Cronet Async API. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Sync Created 6 years, 1 month 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
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 android.util.Log;
8
9 import org.chromium.base.CalledByNative;
10 import org.chromium.base.JNINamespace;
11
12 import java.nio.ByteBuffer;
13 import java.util.AbstractMap;
14 import java.util.ArrayList;
15 import java.util.HashMap;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.concurrent.Executor;
19
7 /** 20 /**
8 * UrlRequest using Chromium HTTP stack implementation. 21 * UrlRequest using Chromium HTTP stack implementation. Could be accessed from
22 * any thread on Executor. Cancel can be done from any thread.
23 * All @CallByNative methods are called on native network thread
24 * and post tasks with listener calls onto Executor. Upon return from listener
25 * callback native request adapter is called on executive thread and posts
26 * native tasks to native network thread. Because Cancel could be called from
27 * any thread it is protected by mUrlRequestAdapterLock.
mmenke 2014/11/06 16:08:27 I think this comment can be cleaned up. I can pro
mef 2014/11/06 17:07:47 I'll appreciate that.
9 */ 28 */
10 public class CronetUrlRequest implements UrlRequest { 29 @JNINamespace("cronet")
30 final class CronetUrlRequest implements UrlRequest {
31 /** Native adapter object, owned by UrlRequest. */
mmenke 2014/11/06 16:08:27 Should this use the "/**"? I assume the others do
mef 2014/11/06 22:51:46 Done.
32 private long mUrlRequestAdapter;
33 private boolean mStarted = false;
34 private boolean mCanceled = false;
35 private boolean mInOnDataReceived = false;
36 private boolean mDestroyRequestAdapterAfterOnDataReceived = false;
37 /*
38 * Synchronize access to mUrlRequestAdapter, mStarted, mCanceled and
39 * mDestroyAfterReading.
40 */
41 private final Object mUrlRequestAdapterLock = new Object();
42 private final CronetUrlRequestContext mRequestContext;
43 private final Executor mExecutor;
44 /*
mmenke 2014/11/06 16:08:27 Suggest a blank line before each of these multi-li
mef 2014/11/06 22:51:47 Done.
45 * Url chain contans the URL currently being requested, and
46 * all URLs previously requested. New URLs are only added after it is
47 * decided a redirect will be followed.
48 */
49 private final List<String> mUrlChain = new ArrayList<String>();
50
51 private final UrlRequestListener mListener;
52 private final String mInitialUrl;
53 private final int mPriority;
54 private String mInitialMethod;
55 private final HeadersList mRequestHeaders = new HeadersList();
56
57 private NativeResponseInfo mResponseInfo;
58
59 /*
60 * Listener callback is repeatedly called when data is received, so it is
61 * cached as member variable.
62 */
63 private OnDataReceivedRunnable mOnDataReceivedTask;
64
65 static final class HeaderEntry extends
66 AbstractMap.SimpleEntry<String, String> {
67 public HeaderEntry(String name, String value) {
68 super(name, value);
69 }
70 }
71
72 static final class HeadersList extends ArrayList<HeaderEntry> {
73 }
74
75 final class OnDataReceivedRunnable implements Runnable {
76 ByteBuffer mByteBuffer;
77
78 public void run() {
79 if (isCanceled()) {
80 return;
81 }
82 try {
83 synchronized (mUrlRequestAdapterLock) {
84 if (mUrlRequestAdapter == 0) {
85 return;
86 }
87 mInOnDataReceived = true;
mmenke 2014/11/06 16:08:27 Should explicitly mention why this is needed.
mef 2014/11/06 22:51:46 Done.
88 }
89 mListener.onDataReceived(CronetUrlRequest.this,
90 mResponseInfo, mByteBuffer);
91 mByteBuffer = null;
92 synchronized (mUrlRequestAdapterLock) {
93 mInOnDataReceived = false;
94 if (mDestroyRequestAdapterAfterOnDataReceived) {
95 destroyRequestAdapter();
96 }
97 if (isCanceled()) {
mmenke 2014/11/06 16:08:27 Can just merge these and get rid of mDestroyReques
mef 2014/11/06 22:51:46 Done.
98 return;
99 }
100 nativeReceiveData(mUrlRequestAdapter);
101 }
102 } catch (Exception e) {
103 synchronized (mUrlRequestAdapterLock) {
104 mInOnDataReceived = false;
105 if (mDestroyRequestAdapterAfterOnDataReceived) {
106 destroyRequestAdapter();
107 }
108 }
109 onCalledByNativeException(e);
110 }
111 }
112 }
113
114 static final class NativeResponseInfo implements ResponseInfo {
115 private final String[] mResponseInfoUrlChain;
116 private final int mHttpStatusCode;
117 private final HeadersMap mAllHeaders = new HeadersMap();
118 private final boolean mWasCached;
119 private final String mNegotiatedProtocol;
120
121 NativeResponseInfo(String[] urlChain, int httpStatusCode,
122 boolean wasCached, String negotiatedProtocol) {
123 mResponseInfoUrlChain = urlChain;
124 mHttpStatusCode = httpStatusCode;
125 mWasCached = wasCached;
126 mNegotiatedProtocol = negotiatedProtocol;
127 }
128
129 @Override
130 public String getUrl() {
131 return mResponseInfoUrlChain[mResponseInfoUrlChain.length - 1];
132 }
133
134 @Override
135 public String[] getUrlChain() {
136 return mResponseInfoUrlChain;
137 }
138
139 @Override
140 public int getHttpStatusCode() {
141 return mHttpStatusCode;
142 }
143
144 @Override
145 public Map<String, List<String>> getAllHeaders() {
146 return mAllHeaders;
147 }
148
149 @Override
150 public boolean wasCached() {
151 return mWasCached;
152 }
153
154 @Override
155 public String getNegotiatedProtocol() {
156 return mNegotiatedProtocol;
157 }
158 };
159
160 static final class NativeExtendedResponseInfo implements
161 ExtendedResponseInfo {
162 private final ResponseInfo mResponseInfo;
163 private final long mTotalReceivedBytes;
164
165 NativeExtendedResponseInfo(ResponseInfo responseInfo,
166 long totalReceivedBytes) {
167 mResponseInfo = responseInfo;
168 mTotalReceivedBytes = totalReceivedBytes;
169 }
170
171 @Override
172 public ResponseInfo getResponseInfo() {
173 return mResponseInfo;
174 }
175
176 @Override
177 public long getTotalReceivedBytes() {
178 return mTotalReceivedBytes;
179 }
180 };
181
182 CronetUrlRequest(CronetUrlRequestContext requestContext,
183 long urlRequestContextAdapter,
184 String url,
185 int priority,
186 UrlRequestListener listener,
187 Executor executor) {
188 if (url == null) {
189 throw new NullPointerException("URL is required");
190 }
191 if (listener == null) {
192 throw new NullPointerException("Listener is required");
193 }
194 if (executor == null) {
195 throw new NullPointerException("Executor is required");
196 }
197
198 mRequestContext = requestContext;
199 mInitialUrl = url;
200 mUrlChain.add(url);
201 mPriority = convertRequestPriority(priority);
202 mListener = listener;
203 mExecutor = executor;
204 }
205
11 @Override 206 @Override
12 public void setHttpMethod(String method) { 207 public void setHttpMethod(String method) {
13 208 checkNotStarted();
209 if (method == null) {
210 throw new NullPointerException("Method is required.");
211 }
212 mInitialMethod = method;
14 } 213 }
15 214
16 @Override 215 @Override
17 public void addHeader(String header, String value) { 216 public void addHeader(String header, String value) {
18 217 checkNotStarted();
19 } 218 if (header == null) {
20 219 throw new NullPointerException("Invalid header name.");
21 @Override 220 }
22 public void start(UrlRequestListener listener) { 221 if (value == null) {
23 222 throw new NullPointerException("Invalid header value.");
223 }
224 mRequestHeaders.add(new HeaderEntry(header, value));
225 }
226
227 @Override
228 public void start() {
229 synchronized (mUrlRequestAdapterLock) {
230 if (mUrlRequestAdapter != 0) {
mmenke 2014/11/06 16:08:27 Should check mStarted instead (It prevents restart
mef 2014/11/06 22:51:46 Done.
231 throw new IllegalStateException("Request is already started.");
232 }
233 mUrlRequestAdapter = nativeCreateRequestAdapter(
234 mRequestContext.getUrlRequestContextAdapter(),
235 mInitialUrl,
236 mPriority);
237 if (mInitialMethod != null) {
238 if (!nativeSetHttpMethod(mUrlRequestAdapter, mInitialMethod)) {
239 nativeDestroyRequestAdapter(mUrlRequestAdapter);
240 mUrlRequestAdapter = 0;
mmenke 2014/11/06 16:08:27 Replace these two lines with destroyRequestAdapter
mef 2014/11/06 22:51:46 Done.
241 throw new IllegalArgumentException("Invalid http method "
242 + mInitialMethod);
243 }
244 }
245 for (HeaderEntry header : mRequestHeaders) {
246 if (!nativeAddHeader(mUrlRequestAdapter, header.getKey(),
247 header.getValue())) {
248 nativeDestroyRequestAdapter(mUrlRequestAdapter);
249 mUrlRequestAdapter = 0;
250 throw new IllegalArgumentException("Invalid header "
251 + header.getKey() + "=" + header.getValue());
252 }
253 }
254 mStarted = true;
255 nativeStart(mUrlRequestAdapter);
256 mRequestContext.onRequestStarted(this);
257 }
24 } 258 }
25 259
26 @Override 260 @Override
27 public void cancel() { 261 public void cancel() {
28 262 synchronized (mUrlRequestAdapterLock) {
263 if (mCanceled) {
264 return;
265 }
266 mCanceled = true;
267 // During call into listener OnDataReceived adapter cannot be
268 // destroyed as it owns the byte buffer.
269 if (mInOnDataReceived) {
270 mDestroyRequestAdapterAfterOnDataReceived = true;
271 } else {
272 destroyRequestAdapter();
273 }
274 }
29 } 275 }
30 276
31 @Override 277 @Override
32 public boolean isCanceled() { 278 public boolean isCanceled() {
33 return false; 279 synchronized (mUrlRequestAdapterLock) {
280 return mCanceled;
281 }
34 } 282 }
35 283
36 @Override 284 @Override
37 public void pause() { 285 public void pause() {
38 286 throw new UnsupportedOperationException("Not implemented yet");
39 } 287 }
40 288
41 @Override 289 @Override
42 public boolean isPaused() { 290 public boolean isPaused() {
43 return false; 291 return false;
44 } 292 }
45 293
46 @Override 294 @Override
47 public void resume() { 295 public void resume() {
48 296 throw new UnsupportedOperationException("Not implemented yet");
297 }
298
299 /**
300 * Post task to application Executor. Used for Listener callbacks
301 * and other tasks that should not be executed on network thread.
302 */
303 private void postTaskToExecutor(Runnable task) {
304 mExecutor.execute(task);
305 }
306
307 private static int convertRequestPriority(int priority) {
308 switch (priority) {
309 case REQUEST_PRIORITY_IDLE:
310 return RequestPriority.IDLE;
311 case REQUEST_PRIORITY_LOWEST:
312 return RequestPriority.LOWEST;
313 case REQUEST_PRIORITY_LOW:
314 return RequestPriority.LOW;
315 case REQUEST_PRIORITY_MEDIUM:
316 return RequestPriority.MEDIUM;
317 case REQUEST_PRIORITY_HIGHEST:
318 return RequestPriority.HIGHEST;
319 default:
320 return RequestPriority.MEDIUM;
321 }
322 }
323
324 private NativeResponseInfo prepareResponseInfo(int httpStatusCode) {
mmenke 2014/11/06 16:08:27 Should either rename network thread methods to mak
mef 2014/11/06 22:51:46 Done.
325 long urlRequestAdapter;
326 synchronized (mUrlRequestAdapterLock) {
327 if (mUrlRequestAdapter == 0) {
328 return null;
329 }
330 urlRequestAdapter = mUrlRequestAdapter;
331 }
332 NativeResponseInfo responseInfo = new NativeResponseInfo(
333 mUrlChain.toArray(new String[mUrlChain.size()]),
334 httpStatusCode,
335 nativeGetWasCached(urlRequestAdapter),
336 nativeGetNegotiatedProtocol(urlRequestAdapter));
mmenke 2014/11/06 16:08:27 Should mention why all these calls into the adapte
mef 2014/11/06 22:51:46 Done.
337 nativePopulateResponseHeaders(urlRequestAdapter,
338 responseInfo.mAllHeaders);
339 return responseInfo;
340 }
341
342 private void checkNotStarted() {
343 synchronized (mUrlRequestAdapterLock) {
344 if (mStarted) {
345 throw new IllegalStateException("Request is already started.");
346 }
mmenke 2014/11/06 16:08:26 Should we check if we're cancelled, too? Seems li
mef 2014/11/06 22:51:46 Done.
347 }
348 }
349
350 private void destroyRequestAdapter() {
351 synchronized (mUrlRequestAdapterLock) {
352 if (mUrlRequestAdapter == 0) {
353 return;
354 }
355 nativeDestroyRequestAdapter(mUrlRequestAdapter);
356 mRequestContext.onRequestDestroyed(this);
357 mUrlRequestAdapter = 0;
358 }
359 }
360
361 private void appendHeaderToMap(HeadersMap headersMap,
mmenke 2014/11/06 16:08:27 static...Actually, I suggest just inlining this, t
mef 2014/11/06 22:51:46 Done.
362 String name, String value) {
363 if (!headersMap.containsKey(name)) {
364 headersMap.put(name, new ArrayList<String>());
365 }
366 headersMap.get(name).add(value);
367 }
368
369 /**
370 * If @CalledByNative method throws an exception, request gets canceled
371 * and exception could be retrieved from request using getException().
mmenke 2014/11/06 16:08:27 "Only called on the Executor".
mmenke 2014/11/06 16:08:27 There is no GetException method. We actually pass
mef 2014/11/06 22:51:46 Done.
372 */
373 private void onCalledByNativeException(Exception e) {
mmenke 2014/11/06 16:08:27 If the request has already been cancelled, we shou
mmenke 2014/11/06 16:08:27 Both the name and description aren't terribly accu
mef 2014/11/06 22:51:46 Done.
374 UrlRequestException requestError = new UrlRequestException(
375 "CalledByNative method has thrown an exception", e);
376 Log.e(CronetUrlRequestContext.LOG_TAG,
377 "Exception in CalledByNative method", e);
378 try {
379 cancel();
380 mListener.onFailed(this, mResponseInfo, requestError);
381 } catch (Exception cancelException) {
382 Log.e(CronetUrlRequestContext.LOG_TAG,
383 "Exception trying to cancel request", cancelException);
384 }
385 }
386
387 ////////////////////////////////////////////////
388 // Private methods called by the native code.
389 ////////////////////////////////////////////////
390
391 /**
392 * Called before following redirects. The redirect will automatically be
393 * followed, unless the request is paused or canceled during this
394 * callback. If the redirect response has a body, it will be ignored.
395 * This will only be called between start and onResponseStarted.
396 *
397 * @param newLocation Location where request is redirected.
398 * @param httpStatusCode from redirect response
399 */
400 @SuppressWarnings("unused")
401 @CalledByNative
402 private void onRedirect(final String newLocation, int httpStatusCode) {
403 final NativeResponseInfo responseInfo =
404 prepareResponseInfo(httpStatusCode);
405 Runnable task = new Runnable() {
406 public void run() {
407 if (isCanceled()) {
408 return;
409 }
410 try {
411 mListener.onRedirect(CronetUrlRequest.this, responseInfo,
412 newLocation);
413 synchronized (mUrlRequestAdapterLock) {
414 if (isCanceled()) {
415 return;
416 }
417 // It is Ok to access mUrlChain not on the network
418 // thread as the request is waiting to follow redirect.
419 mUrlChain.add(newLocation);
420 nativeFollowDeferredRedirect(mUrlRequestAdapter);
421 }
422 } catch (Exception e) {
423 onCalledByNativeException(e);
424 }
425 }
426 };
427 postTaskToExecutor(task);
428 }
429
430 /**
431 * Called when the final set of headers, after all redirects,
432 * is received. Can only be called once for each request.
433 */
434 @SuppressWarnings("unused")
435 @CalledByNative
436 private void onResponseStarted(int httpStatusCode) {
437 mResponseInfo = prepareResponseInfo(httpStatusCode);
438 Runnable task = new Runnable() {
439 public void run() {
440 if (isCanceled()) {
441 return;
442 }
443 try {
444 mListener.onResponseStarted(CronetUrlRequest.this,
445 mResponseInfo);
446 synchronized (mUrlRequestAdapterLock) {
447 if (isCanceled()) {
448 return;
449 }
450 nativeReceiveData(mUrlRequestAdapter);
451 }
452 } catch (Exception e) {
453 onCalledByNativeException(e);
454 }
455 }
456 };
457 postTaskToExecutor(task);
458 }
459
460 /**
461 * Called whenever data is received. The ByteBuffer remains
462 * valid only until listener callback. Or if the callback
463 * pauses the request, it remains valid until the request is resumed.
464 * Cancelling the request also invalidates the buffer.
465 *
466 * @param byteBuffer Received data.
467 */
468 @SuppressWarnings("unused")
469 @CalledByNative
470 private void onDataReceived(final ByteBuffer byteBuffer) {
471 if (mOnDataReceivedTask == null) {
472 mOnDataReceivedTask = new OnDataReceivedRunnable();
473 }
474 mOnDataReceivedTask.mByteBuffer = byteBuffer;
475 postTaskToExecutor(mOnDataReceivedTask);
476 }
477
478 /**
479 * Called when request is complete, no callbacks will be called afterwards.
480 */
481 @SuppressWarnings("unused")
482 @CalledByNative
483 private void onComplete() {
mmenke 2014/11/06 16:08:27 onSucceeded?
mef 2014/11/06 22:51:47 Done.
484 long totalReceivedBytes;
485 synchronized (mUrlRequestAdapterLock) {
486 if (mUrlRequestAdapter == 0) {
487 return;
488 }
489 totalReceivedBytes =
490 nativeGetTotalReceivedBytes(mUrlRequestAdapter);
491 }
492
493 final NativeExtendedResponseInfo extendedResponseInfo =
494 new NativeExtendedResponseInfo(mResponseInfo,
495 totalReceivedBytes);
496 Runnable task = new Runnable() {
497 public void run() {
498 if (isCanceled()) {
499 return;
500 }
501 // Destroy adapter first, so request context could be shut down
502 // from the listener.
503 destroyRequestAdapter();
mmenke 2014/11/06 16:08:27 Hrm...Does it make sense to grab the lock, check i
mef 2014/11/06 22:51:46 Done.
504 try {
505 mListener.onSucceeded(CronetUrlRequest.this,
506 extendedResponseInfo);
507 } catch (Exception e) {
508 Log.e(CronetUrlRequestContext.LOG_TAG,
509 "Exception in onComplete method", e);
510 }
511 }
512 };
513 postTaskToExecutor(task);
514 }
515
516 /**
517 * Called when error has occured, no callbacks will be called afterwards.
518 *
519 * @param nativeError native net error code.
520 * @param errorString textual representation of the error code.
521 */
522 @SuppressWarnings("unused")
523 @CalledByNative
524 private void onError(final int nativeError, final String errorString) {
525 Runnable task = new Runnable() {
526 public void run() {
527 if (isCanceled()) {
528 return;
529 }
530 // Destroy adapter first, so request context could be shut down
531 // from the listener.
532 destroyRequestAdapter();
533 try {
534 UrlRequestException requestError = new UrlRequestException(
535 "Exception in CronetUrlRequest: " + errorString,
536 nativeError);
537 mListener.onFailed(CronetUrlRequest.this,
538 mResponseInfo,
539 requestError);
540 } catch (Exception e) {
541 Log.e(CronetUrlRequestContext.LOG_TAG,
542 "Exception in onError method", e);
543 }
544 }
545 };
546 postTaskToExecutor(task);
547 }
548
549 /**
550 * Appends header |name| with value |value| to |headersMap|.
551 */
552 @SuppressWarnings("unused")
553 @CalledByNative
554 private void onAppendResponseHeader(HeadersMap headersMap,
555 String name, String value) {
556 try {
mmenke 2014/11/06 16:08:27 Is this try block even needed? This is a weird en
mef 2014/11/06 17:07:47 Hmm, maybe not.
mef 2014/11/06 22:51:47 Done.
557 appendHeaderToMap(headersMap, name, value);
558 } catch (final Exception e) {
559 onCalledByNativeException(e);
560 Runnable task = new Runnable() {
561 public void run() {
562 if (isCanceled()) {
563 return;
564 }
565 onCalledByNativeException(e);
566 }
567 };
568 postTaskToExecutor(task);
569 }
570 }
571
572 // Native methods are implemented in cronet_url_request.cc.
573
574 private native long nativeCreateRequestAdapter(
575 long urlRequestContextAdapter, String url, int priority);
576
577 private native boolean nativeAddHeader(long urlRequestAdapter, String name,
578 String value);
579
580 private native boolean nativeSetHttpMethod(long urlRequestAdapter,
581 String method);
582
583 private native void nativeStart(long urlRequestAdapter);
584
585 private native void nativeDestroyRequestAdapter(long urlRequestAdapter);
586
587 private native void nativeFollowDeferredRedirect(long urlRequestAdapter);
588
589 private native void nativeReceiveData(long urlRequestAdapter);
590
591 private native void nativePopulateResponseHeaders(long urlRequestAdapter,
592 HeadersMap headers);
593
594 private native String nativeGetNegotiatedProtocol(long urlRequestAdapter);
595
596 private native boolean nativeGetWasCached(long urlRequestAdapter);
597
598 private native long nativeGetTotalReceivedBytes(long urlRequestAdapter);
599
600 // Explicit class to work around JNI-generator generics confusion.
601 private static class HeadersMap extends HashMap<String, List<String>> {
49 } 602 }
50 } 603 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698