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

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