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

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: More tests, more comments, more cancel. 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 CronetUrlRequestContext mRequestContext;
29 private final List<String> mUrlChain = new ArrayList<String>();
xunjieli 2014/10/23 15:51:22 mUrlChain is changed in onRedirect(). Is it okay t
mef 2014/10/24 03:31:45 Good question. Compiler doesn't complain, but I'm
mmenke 2014/10/24 03:46:19 Regardless of guidelines, I think it makes sense j
mef 2014/10/27 21:00:02 On a closer look we don't pass mUrlChain to listen
30 private final int mPriority;
31 private final UrlRequestListener mListener;
32 private final Executor mExecutor;
33 private NativeResponseInfo mResponseInfo;
34 private boolean mStarted = false;
35 private AtomicBoolean mCanceled = new AtomicBoolean(false);
36 private OnDataReceivedRunnable mOnDataReceivedTask;
37
38 final class OnDataReceivedRunnable implements Runnable {
39 ByteBuffer mByteBuffer;
40 public void run() {
41 if (isCanceled()) {
42 return;
43 }
44 try {
45 mListener.onDataReceived(CronetUrlRequest.this,
46 mResponseInfo, mByteBuffer);
47 mByteBuffer = null;
48 if (!isCanceled()) {
49 nativeReceiveData(mUrlRequestAdapter);
50 }
51 } catch (Exception e) {
52 onCalledByNativeException(e);
53 }
54 }
55 }
56
57 final class NativeResponseInfo implements ResponseInfo {
58 private final String[] mUrlChain;
xunjieli 2014/10/23 15:51:22 nit: this variable has the same name as the one in
mef 2014/10/24 03:31:44 Done.
59 private final int mHttpStatusCode;
60 private final ResponseHeadersMap mAllHeaders = new ResponseHeadersMap();
61 private final boolean mWasCached;
62 private final String mNegotiatedProtocol;
63
64 NativeResponseInfo(String[] urlChain, int httpStatusCode,
65 boolean wasCached, String negotiatedProtocol) {
66 mUrlChain = urlChain;
67 mHttpStatusCode = httpStatusCode;
68 mWasCached = wasCached;
69 mNegotiatedProtocol = negotiatedProtocol;
70 }
71
72 @Override
73 public String getUrl() {
74 return mUrlChain[mUrlChain.length - 1];
75 }
76
77 @Override
78 public String[] getUrlChain() {
79 return mUrlChain;
80 }
81
82 @Override
83 public int getHttpStatusCode() {
84 return mHttpStatusCode;
85 }
86
87 @Override
88 public Map<String, List<String>> getAllHeaders() {
89 return mAllHeaders;
90 }
91
92 @Override
93 public boolean wasCached() {
94 return mWasCached;
95 }
96
97 @Override
98 public String getNegotiatedProtocol() {
99 return mNegotiatedProtocol;
100 }
101 };
102
103 final class NativeExtendedResponseInfo implements ExtendedResponseInfo {
104 ResponseInfo mResponseInfo;
105 private long mTotalReceivedBytes = 0;
106
107 NativeExtendedResponseInfo(ResponseInfo responseInfo,
108 long totalReceivedBytes) {
109 mResponseInfo = responseInfo;
110 mTotalReceivedBytes = totalReceivedBytes;
111 }
112
113 @Override
114 public ResponseInfo getResponseInfo() {
115 return mResponseInfo;
116 }
117
118 @Override
119 public long getTotalReceivedBytes() {
120 return mTotalReceivedBytes;
121 }
122 };
123
124
125 CronetUrlRequest(CronetUrlRequestContext requestContext,
xunjieli 2014/10/23 15:51:22 Just curious. Why is it that CronetUrlRequest cons
mmenke 2014/10/23 21:36:42 In Java, by default, methods and variables are sco
xunjieli 2014/10/23 21:44:46 I see, so we want to force clients to create a req
mef 2014/10/24 03:31:44 Acknowledged.
126 String url, int priority,
127 UrlRequestListener listener,
128 Executor executor) {
129 if (requestContext == null) {
130 throw new NullPointerException("Context is required");
131 }
132 if (url == null) {
133 throw new NullPointerException("URL is required");
134 }
135 if (listener == null) {
136 throw new NullPointerException("Listener is required");
137 }
138 if (executor == null) {
139 throw new NullPointerException("Executor is required");
140 }
141
142 mRequestContext = requestContext;
143 mUrlChain.add(url);
144 mPriority = convertRequestPriority(priority);
145 mUrlRequestAdapter = nativeCreateRequestAdapter(
146 mRequestContext.getCronetUrlRequestContextAdapter(),
147 url,
148 mPriority);
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 checkNotStartedOrDone();
156 if (method == null) {
157 throw new NullPointerException("Method is required.");
158 }
159 nativeSetHttpMethod(mUrlRequestAdapter, 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 checkNotStartedOrDone();
165 if (header == null || value == null) {
166 throw new NullPointerException("Invalid header.");
167 }
168 if (!nativeAddHeader(mUrlRequestAdapter, header, value)) {
169 throw new IllegalArgumentException("Invalid header.");
170 }
19 } 171 }
20 172
21 @Override 173 @Override
22 public void start(UrlRequestListener listener) { 174 public void start() {
23 175 checkNotStartedOrDone();
176 mStarted = true;
177 nativeStart(mUrlRequestAdapter);
24 } 178 }
25 179
26 @Override 180 @Override
27 public void cancel() { 181 public void cancel() {
28 182 if (mCanceled.compareAndSet(false, true)) {
183 nativeCancel(mUrlRequestAdapter);
184 }
29 } 185 }
30 186
31 @Override 187 @Override
32 public boolean isCanceled() { 188 public boolean isCanceled() {
33 return false; 189 return mCanceled.get();
34 } 190 }
35 191
36 @Override 192 @Override
37 public void pause() { 193 public void pause() {
38 194 throw new UnsupportedOperationException("Not implemented yet");
39 } 195 }
40 196
41 @Override 197 @Override
42 public boolean isPaused() { 198 public boolean isPaused() {
43 return false; 199 return false;
44 } 200 }
45 201
46 @Override 202 @Override
47 public void resume() { 203 public void resume() {
48 204 throw new UnsupportedOperationException("Not implemented yet");
205 }
206
207 private void postAppTask(Runnable task) {
xunjieli 2014/10/23 14:04:33 nit: the name "postAppTask" sounds a little vague
mef 2014/10/24 03:31:45 Done.
208 mExecutor.execute(task);
209 }
210
211 private static int convertRequestPriority(int priority) {
212 switch (priority) {
213 case REQUEST_PRIORITY_IDLE:
214 return ChromiumUrlRequestPriority.IDLE;
215 case REQUEST_PRIORITY_LOWEST:
216 return ChromiumUrlRequestPriority.LOWEST;
217 case REQUEST_PRIORITY_LOW:
218 return ChromiumUrlRequestPriority.LOW;
219 case REQUEST_PRIORITY_MEDIUM:
220 return ChromiumUrlRequestPriority.MEDIUM;
221 case REQUEST_PRIORITY_HIGHEST:
222 return ChromiumUrlRequestPriority.HIGHEST;
223 default:
224 return ChromiumUrlRequestPriority.MEDIUM;
225 }
226 }
227
228 private NativeResponseInfo prepareResponseInfo() {
229 NativeResponseInfo responseInfo = new NativeResponseInfo(
230 mUrlChain.toArray(new String[mUrlChain.size()]),
231 nativeGetHttpStatusCode(mUrlRequestAdapter),
232 nativeGetWasCached(mUrlRequestAdapter),
233 nativeGetNegotiatedProtocol(mUrlRequestAdapter));
234 nativePopulateResponseHeaders(mUrlRequestAdapter,
235 responseInfo.mAllHeaders);
236 return responseInfo;
237 }
238
239 private void checkNotStartedOrDone() {
240 if (mStarted) {
241 throw new IllegalStateException("Request is already started.");
242 }
243 if (mUrlRequestAdapter == 0) {
244 throw new IllegalStateException("Request is already destroyed.");
245 }
246 }
247
248 private void destroyRequestAdapter() {
249 if (mUrlRequestAdapter != 0) {
250 nativeDestroyRequestAdapter(mUrlRequestAdapter);
251 mUrlRequestAdapter = 0;
252 } else {
253 throw new IllegalStateException("Request is already destroyed.");
254 }
255 }
256
257 /**
258 * If @CalledByNative method throws an exception, request gets cancelled
259 * and exception could be retrieved from request using getException().
260 */
261 private void onCalledByNativeException(Exception e) {
262 UrlRequestException requestError = new UrlRequestException(
263 "CalledByNative method has thrown an exception", e);
264 Log.e(CronetUrlRequestContext.LOG_TAG,
265 "Exception in CalledByNative method", e);
xunjieli 2014/10/23 15:51:22 nit: indent.
mef 2014/10/24 03:31:45 Done.
266 try {
267 cancel();
268 mListener.onError(this, mResponseInfo, requestError);
269 } catch (Exception cancelException) {
270 Log.e(CronetUrlRequestContext.LOG_TAG,
271 "Exception trying to cancel request", cancelException);
xunjieli 2014/10/23 15:51:21 nit: indent.
mef 2014/10/24 03:31:45 Done.
272 }
273 }
274
275 ////////////////////////////////////////////////
276 // Private methods called by the native code.
277 ////////////////////////////////////////////////
278
279 /**
280 * Called before following redirects. The redirect will automatically be
xunjieli 2014/10/23 14:04:33 nit: there are two spaces after "."
mef 2014/10/24 03:31:45 Done.
281 * followed, unless the request is paused or cancelled during this
282 * callback. If the redirect response has a body, it will be ignored.
283 * This will only be called between start and onResponseStarted.
284 *
285 * @param newLocation Location where request is redirected.
286 */
287 @SuppressWarnings("unused")
288 @CalledByNative
289 private void onRedirect(final String newLocation) {
290 mUrlChain.add(newLocation);
291 final NativeResponseInfo responseInfo = prepareResponseInfo();
292 Runnable task = new Runnable() {
293 public void run() {
294 if (isCanceled()) {
295 return;
296 }
297 try {
298 mListener.onRedirect(CronetUrlRequest.this, responseInfo,
299 new URL(newLocation));
300 if (!isCanceled()) {
301 nativeFollowDeferredRedirect(mUrlRequestAdapter);
302 }
303 } catch (Exception e) {
304 onCalledByNativeException(e);
305 }
306 }
307 };
308 postAppTask(task);
309 }
310
311 /**
312 * Called when the final set of headers, after all redirects,
313 * is received. Can only be called once for each request.
314 */
315 @SuppressWarnings("unused")
316 @CalledByNative
317 private void onResponseStarted() {
318 mResponseInfo = prepareResponseInfo();
319 Runnable task = new Runnable() {
320 public void run() {
321 if (isCanceled()) {
322 return;
323 }
324 try {
325 mListener.onResponseStarted(CronetUrlRequest.this,
326 mResponseInfo);
327 if (!isCanceled()) {
328 nativeReceiveData(mUrlRequestAdapter);
329 }
330 } catch (Exception e) {
331 onCalledByNativeException(e);
332 }
333 }
334 };
335 postAppTask(task);
336 }
337
338 /**
339 * Called whenever data is received. The ByteBuffer remains
340 * valid only until listener callback. Or if the callback
341 * pauses the request, it remains valid until the request is resumed.
342 * Cancelling the request also invalidates the buffer.
343 *
344 * @param byteBuffer Received data.
345 */
346 @SuppressWarnings("unused")
347 @CalledByNative
348 private void onDataReceived(final ByteBuffer byteBuffer) {
349 if (mOnDataReceivedTask == null) {
350 mOnDataReceivedTask = new OnDataReceivedRunnable();
351 }
352 mOnDataReceivedTask.mByteBuffer = byteBuffer;
353 postAppTask(mOnDataReceivedTask);
354 }
355
356 /**
357 * Called when request is canceled, no callbacks are called.
358 */
359 @SuppressWarnings("unused")
360 @CalledByNative
361 private void onCanceled() {
362 Runnable task = new Runnable() {
363 public void run() {
364 destroyRequestAdapter();
365 }
366 };
367 postAppTask(task);
368 }
369
370 /**
371 * Called when request is complete, no callbacks will be called afterwards.
372 */
373 @SuppressWarnings("unused")
374 @CalledByNative
375 private void onComplete() {
376 final NativeExtendedResponseInfo extendedResponseInfo =
377 new NativeExtendedResponseInfo(mResponseInfo,
378 nativeGetTotalReceivedBytes(mUrlRequestAdapter));
xunjieli 2014/10/23 15:51:22 nit: indent. nativeGetTotalReceivedBytes should be
mef 2014/10/24 03:31:45 Hmm, I'm not sure, it wouldn't fit there, would it
379 Runnable task = new Runnable() {
380 public void run() {
381 if (isCanceled()) {
382 return;
383 }
384 try {
385 mListener.onComplete(CronetUrlRequest.this,
386 extendedResponseInfo);
387 } catch (Exception e) {
388 Log.e(CronetUrlRequestContext.LOG_TAG,
389 "Exception in onComplete method", e);
xunjieli 2014/10/23 15:51:22 nit: indent.
mef 2014/10/24 03:31:44 Done.
390 }
391 destroyRequestAdapter();
392 }
393 };
394 postAppTask(task);
395 }
396
397 /**
398 * Called when error has occured, no callbacks will be called afterwards.
399 *
400 * @param nativeError native net error code.
401 * @param errorString textual representation of the error code.
402 */
403 @SuppressWarnings("unused")
404 @CalledByNative
405 private void onError(final int nativeError, final String errorString) {
406 Runnable task = new Runnable() {
407 public void run() {
408 if (isCanceled()) {
409 return;
410 }
411 try {
412 UrlRequestException requestError = new UrlRequestException(
413 "Exception in CronetUrlRequest: " + errorString,
414 nativeError);
415 mListener.onError(CronetUrlRequest.this,
416 mResponseInfo,
417 requestError);
418 destroyRequestAdapter();
419 } catch (Exception e) {
420 onCalledByNativeException(e);
421 }
422 }
423 };
424 postAppTask(task);
425 }
426
427 /**
428 * Appends header |name| with value |value| to |headersMap|.
429 */
430 @SuppressWarnings("unused")
431 @CalledByNative
432 private void onAppendResponseHeader(ResponseHeadersMap headersMap,
433 String name, String value) {
434 try {
435 if (!headersMap.containsKey(name)) {
436 headersMap.put(name, new ArrayList<String>());
437 }
438 headersMap.get(name).add(value);
439 } catch (Exception e) {
440 onCalledByNativeException(e);
441 }
442 }
443
444 // Native methods are implemented in cronet_url_request.cc.
445
446 private native long nativeCreateRequestAdapter(
447 long urlRequestContextAdapter, String url, int priority);
448
449 private native boolean nativeAddHeader(long urlRequestAdapter, String name,
450 String value);
451
452 private native void nativeSetHttpMethod(long urlRequestAdapter,
453 String method);
454
455 private native void nativeStart(long urlRequestAdapter);
456
457 private native void nativeCancel(long urlRequestAdapter);
458
459 private native void nativeDestroyRequestAdapter(long urlRequestAdapter);
460
461 private native void nativeFollowDeferredRedirect(long urlRequestAdapter);
462
463 private native void nativeReceiveData(long urlRequestAdapter);
464
465 private native int nativeGetHttpStatusCode(long urlRequestAdapter);
466
467 private native void nativePopulateResponseHeaders(long urlRequestAdapter,
468 ResponseHeadersMap headers);
469
470 private native String nativeGetNegotiatedProtocol(long urlRequestAdapter);
471
472 private native boolean nativeGetWasCached(long urlRequestAdapter);
473
474 private native long nativeGetTotalReceivedBytes(long urlRequestAdapter);
475
476 // Explicit class to work around JNI-generator generics confusion.
477 private class ResponseHeadersMap extends HashMap<String, List<String>> {
49 } 478 }
50 } 479 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698