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

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

Powered by Google App Engine
This is Rietveld 408576698