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

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

Powered by Google App Engine
This is Rietveld 408576698