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

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 review comments. 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.AbstractMap;
15 import java.util.ArrayList;
16 import java.util.HashMap;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.concurrent.Executor;
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 21:40:29 Should mention what this protects access to - shou
mef 2014/10/30 03:01:32 Done.
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 HeadersList mRequestHeaders = new HeadersList();
37 private NativeResponseInfo mResponseInfo;
38 private boolean mStarted = false;
39 private boolean mCanceled = false;
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, int priority,
mmenke 2014/10/29 21:40:29 nit: Seems more consistent to either fit the argu
mef 2014/10/30 03:01:31 Done.
145 UrlRequestListener listener,
146 Executor executor) {
147 if (requestContext == null) {
148 throw new NullPointerException("Context is required");
149 }
150 if (url == null) {
151 throw new NullPointerException("URL is required");
152 }
153 if (listener == null) {
154 throw new NullPointerException("Listener is required");
155 }
156 if (executor == null) {
157 throw new NullPointerException("Executor is required");
158 }
159
160 mRequestContext = requestContext;
161 mInitialUrl = url;
162 mUrlChain.add(url);
163 mPriority = convertRequestPriority(priority);
164 mListener = listener;
165 mExecutor = executor;
166 }
167
11 @Override 168 @Override
12 public void setHttpMethod(String method) { 169 public void setHttpMethod(String method) {
13 170 checkNotStarted();
171 if (method == null) {
mmenke 2014/10/29 21:40:29 Should we check for an empty string, too? Wonder
mef 2014/10/30 03:01:32 Good question, rfc2616 says that 'extension-method
mef 2014/10/30 14:53:17 Done.
172 throw new NullPointerException("Method is required.");
173 }
174 mMethod = method;
14 } 175 }
15 176
16 @Override 177 @Override
17 public void addHeader(String header, String value) { 178 public void addHeader(String header, String value) {
18 179 checkNotStarted();
19 } 180 if (header == null) {
20 181 throw new NullPointerException("Invalid header name.");
21 @Override 182 }
22 public void start(UrlRequestListener listener) { 183 if (value == null) {
23 184 throw new NullPointerException("Invalid header value.");
185 }
186 mRequestHeaders.add(new HeaderEntry(header, value));
187 }
188
189 @Override
190 public void start() {
191 synchronized (mUrlRequestAdapterLock) {
192 if (mUrlRequestAdapter != 0) {
193 throw new IllegalStateException("Request is already started.");
194 }
195 mUrlRequestAdapter = nativeCreateRequestAdapter(
196 mRequestContext.getUrlRequestContextAdapter(),
197 mInitialUrl,
198 mPriority);
199 if (mMethod != null) {
200 nativeSetHttpMethod(mUrlRequestAdapter, mMethod);
201 }
202 for (HeaderEntry header : mRequestHeaders) {
203 if (!nativeAddHeader(mUrlRequestAdapter, header.getKey(),
204 header.getValue())) {
205 nativeDestroyRequestAdapter(mUrlRequestAdapter);
206 mUrlRequestAdapter = 0;
207 throw new IllegalArgumentException("Invalid header "
208 + header.getKey() + "=" + header.getValue());
mmenke 2014/10/29 21:40:29 Binary operators should go on the end of the line
mef 2014/10/30 03:01:31 Yeah, that's what I thought and had, but new Java
xunjieli 2014/10/31 19:31:22 It seems that google java style guide recommends b
209 }
210 }
211 nativeStart(mUrlRequestAdapter);
212 mRequestContext.onRequestStarted(this);
213 mStarted = true;
mmenke 2014/10/29 21:40:29 Should set mStarted to true before calling into na
mef 2014/10/30 03:01:31 Done.
214 }
24 } 215 }
25 216
26 @Override 217 @Override
27 public void cancel() { 218 public void cancel() {
28 219 synchronized (mUrlRequestAdapterLock) {
220 if (mCanceled) {
221 return;
222 }
223 mCanceled = true;
224 destroyRequestAdapter();
225 }
29 } 226 }
30 227
31 @Override 228 @Override
32 public boolean isCanceled() { 229 public boolean isCanceled() {
33 return false; 230 synchronized (mUrlRequestAdapterLock) {
231 return mCanceled;
232 }
34 } 233 }
35 234
36 @Override 235 @Override
37 public void pause() { 236 public void pause() {
38 237 throw new UnsupportedOperationException("Not implemented yet");
39 } 238 }
40 239
41 @Override 240 @Override
42 public boolean isPaused() { 241 public boolean isPaused() {
43 return false; 242 return false;
44 } 243 }
45 244
46 @Override 245 @Override
47 public void resume() { 246 public void resume() {
48 247 throw new UnsupportedOperationException("Not implemented yet");
248 }
249
250 /**
251 * Post task to application Executor or Looper. Used for Listener callbacks
252 * and other tasks that should not be executed on network thread.
253 */
254 private void postAppTask(Runnable task) {
255 mExecutor.execute(task);
256 }
257
258 private static int convertRequestPriority(int priority) {
259 switch (priority) {
260 case REQUEST_PRIORITY_IDLE:
261 return ChromiumUrlRequestPriority.IDLE;
262 case REQUEST_PRIORITY_LOWEST:
263 return ChromiumUrlRequestPriority.LOWEST;
264 case REQUEST_PRIORITY_LOW:
265 return ChromiumUrlRequestPriority.LOW;
266 case REQUEST_PRIORITY_MEDIUM:
267 return ChromiumUrlRequestPriority.MEDIUM;
268 case REQUEST_PRIORITY_HIGHEST:
269 return ChromiumUrlRequestPriority.HIGHEST;
270 default:
271 return ChromiumUrlRequestPriority.MEDIUM;
272 }
273 }
274
275 private NativeResponseInfo prepareResponseInfo(int httpStatusCode) {
276 long urlRequestAdapter;
277 synchronized (mUrlRequestAdapterLock) {
278 if (mUrlRequestAdapter == 0) {
279 return null;
280 }
281 urlRequestAdapter = mUrlRequestAdapter;
282 }
283 NativeResponseInfo responseInfo = new NativeResponseInfo(
284 mUrlChain.toArray(new String[mUrlChain.size()]),
285 httpStatusCode,
286 nativeGetWasCached(urlRequestAdapter),
287 nativeGetNegotiatedProtocol(urlRequestAdapter));
288 nativePopulateResponseHeaders(urlRequestAdapter,
289 responseInfo.mAllHeaders);
290 return responseInfo;
291 }
292
293 private void checkNotStarted() {
294 synchronized (mUrlRequestAdapterLock) {
295 if (mUrlRequestAdapter != 0 || mStarted) {
mmenke 2014/10/29 21:40:29 Can we just check mStarted?
mef 2014/10/30 03:01:31 Done.
296 throw new IllegalStateException("Request is already started.");
297 }
298 }
299 }
300
301 private void destroyRequestAdapter() {
302 synchronized (mUrlRequestAdapterLock) {
303 if (mUrlRequestAdapter == 0) {
304 return;
305 }
306 nativeDestroyRequestAdapter(mUrlRequestAdapter);
307 mRequestContext.onRequestDestroyed(this);
308 mUrlRequestAdapter = 0;
309 }
310 }
311
312 private void appendHeaderToMap(HeadersMap headersMap,
313 String name, String value) {
314 if (!headersMap.containsKey(name)) {
315 headersMap.put(name, new ArrayList<String>());
316 }
317 headersMap.get(name).add(value);
318 }
319
320 /**
321 * If @CalledByNative method throws an exception, request gets cancelled
322 * and exception could be retrieved from request using getException().
323 */
324 private void onCalledByNativeException(Exception e) {
325 UrlRequestException requestError = new UrlRequestException(
326 "CalledByNative method has thrown an exception", e);
327 Log.e(CronetUrlRequestContext.LOG_TAG,
328 "Exception in CalledByNative method", e);
329 try {
330 cancel();
331 mListener.onError(this, mResponseInfo, requestError);
332 } catch (Exception cancelException) {
333 Log.e(CronetUrlRequestContext.LOG_TAG,
334 "Exception trying to cancel request", cancelException);
335 }
336 }
337
338 ////////////////////////////////////////////////
339 // Private methods called by the native code.
340 ////////////////////////////////////////////////
341
342 /**
343 * Called before following redirects. The redirect will automatically be
344 * followed, unless the request is paused or cancelled during this
mmenke 2014/10/29 21:40:29 nit: Comments use canceled, code uses canceled.
mef 2014/10/30 03:01:31 Done.
345 * callback. If the redirect response has a body, it will be ignored.
346 * This will only be called between start and onResponseStarted.
347 *
348 * @param newLocation Location where request is redirected.
349 * @param httpStatusCode from redirect response
350 */
351 @SuppressWarnings("unused")
352 @CalledByNative
353 private void onRedirect(final String newLocation, int httpStatusCode) {
354 mUrlChain.add(newLocation);
mmenke 2014/10/29 21:40:29 If we want to mimic net's behavior, we should only
mef 2014/10/30 03:01:32 Interesting. This is a good place to add while we
mef 2014/10/30 14:53:17 Done.
355 final NativeResponseInfo responseInfo =
356 prepareResponseInfo(httpStatusCode);
357 Runnable task = new Runnable() {
358 public void run() {
359 if (isCanceled()) {
360 return;
361 }
362 try {
363 mListener.onRedirect(CronetUrlRequest.this, responseInfo,
364 new URL(newLocation));
mmenke 2014/10/29 21:40:29 Should we use URLs or Strings? We take in the ini
mef 2014/10/30 03:01:31 Done. I'm voting for Strings as I suspect that con
365 synchronized (mUrlRequestAdapterLock) {
366 if (mUrlRequestAdapter == 0) {
367 return;
368 }
369 nativeFollowDeferredRedirect(mUrlRequestAdapter);
370 }
371 } catch (Exception e) {
372 onCalledByNativeException(e);
373 }
374 }
375 };
376 postAppTask(task);
377 }
378
379 /**
380 * Called when the final set of headers, after all redirects,
381 * is received. Can only be called once for each request.
382 */
383 @SuppressWarnings("unused")
384 @CalledByNative
385 private void onResponseStarted(int httpStatusCode) {
386 mResponseInfo = prepareResponseInfo(httpStatusCode);
387 Runnable task = new Runnable() {
388 public void run() {
389 if (isCanceled()) {
390 return;
391 }
392 try {
393 mListener.onResponseStarted(CronetUrlRequest.this,
394 mResponseInfo);
395 synchronized (mUrlRequestAdapterLock) {
396 if (mUrlRequestAdapter == 0) {
397 return;
398 }
399 nativeReceiveData(mUrlRequestAdapter);
400 }
401 } catch (Exception e) {
402 onCalledByNativeException(e);
403 }
404 }
405 };
406 postAppTask(task);
407 }
408
409 /**
410 * Called whenever data is received. The ByteBuffer remains
411 * valid only until listener callback. Or if the callback
412 * pauses the request, it remains valid until the request is resumed.
413 * Cancelling the request also invalidates the buffer.
414 *
415 * @param byteBuffer Received data.
416 */
417 @SuppressWarnings("unused")
418 @CalledByNative
419 private void onDataReceived(final ByteBuffer byteBuffer) {
420 if (mOnDataReceivedTask == null) {
421 mOnDataReceivedTask = new OnDataReceivedRunnable();
422 }
423 mOnDataReceivedTask.mByteBuffer = byteBuffer;
424 postAppTask(mOnDataReceivedTask);
425 }
426
427 /**
428 * Called when request is complete, no callbacks will be called afterwards.
429 */
430 @SuppressWarnings("unused")
431 @CalledByNative
432 private void onComplete() {
433 long totalReceivedBytes;
434 synchronized (mUrlRequestAdapterLock) {
435 if (mUrlRequestAdapter == 0) {
436 return;
437 }
438 totalReceivedBytes =
439 nativeGetTotalReceivedBytes(mUrlRequestAdapter);
440 }
441
442 final NativeExtendedResponseInfo extendedResponseInfo =
443 new NativeExtendedResponseInfo(mResponseInfo,
444 totalReceivedBytes);
445 Runnable task = new Runnable() {
446 public void run() {
447 if (isCanceled()) {
448 return;
449 }
450 try {
451 mListener.onComplete(CronetUrlRequest.this,
452 extendedResponseInfo);
453 } catch (Exception e) {
454 Log.e(CronetUrlRequestContext.LOG_TAG,
455 "Exception in onComplete method", e);
456 }
457 destroyRequestAdapter();
458 }
459 };
460 postAppTask(task);
461 }
462
463 /**
464 * Called when error has occured, no callbacks will be called afterwards.
465 *
466 * @param nativeError native net error code.
467 * @param errorString textual representation of the error code.
468 */
469 @SuppressWarnings("unused")
470 @CalledByNative
471 private void onError(final int nativeError, final String errorString) {
472 Runnable task = new Runnable() {
473 public void run() {
474 if (isCanceled()) {
475 return;
476 }
477 try {
478 UrlRequestException requestError = new UrlRequestException(
479 "Exception in CronetUrlRequest: " + errorString,
480 nativeError);
481 mListener.onError(CronetUrlRequest.this,
482 mResponseInfo,
483 requestError);
484 destroyRequestAdapter();
485 } catch (Exception e) {
486 onCalledByNativeException(e);
487 }
488 }
489 };
490 postAppTask(task);
491 }
492
493 /**
494 * Appends header |name| with value |value| to |headersMap|.
495 */
496 @SuppressWarnings("unused")
497 @CalledByNative
498 private void onAppendResponseHeader(HeadersMap headersMap,
499 String name, String value) {
500 try {
501 appendHeaderToMap(headersMap, name, value);
502 } catch (final Exception e) {
503 onCalledByNativeException(e);
504 Runnable task = new Runnable() {
505 public void run() {
506 if (isCanceled()) {
507 return;
508 }
509 onCalledByNativeException(e);
510 }
511 };
512 postAppTask(task);
513 }
514 }
515
516 // Native methods are implemented in cronet_url_request.cc.
517
518 private native long nativeCreateRequestAdapter(
519 long urlRequestContextAdapter, String url, int priority);
520
521 private native boolean nativeAddHeader(long urlRequestAdapter, String name,
522 String value);
523
524 private native void nativeSetHttpMethod(long urlRequestAdapter,
525 String method);
526
527 private native void nativeStart(long urlRequestAdapter);
528
529 private native void nativeDestroyRequestAdapter(long urlRequestAdapter);
530
531 private native void nativeFollowDeferredRedirect(long urlRequestAdapter);
532
533 private native void nativeReceiveData(long urlRequestAdapter);
534
535 private native void nativePopulateResponseHeaders(long urlRequestAdapter,
536 HeadersMap headers);
537
538 private native String nativeGetNegotiatedProtocol(long urlRequestAdapter);
539
540 private native boolean nativeGetWasCached(long urlRequestAdapter);
541
542 private native long nativeGetTotalReceivedBytes(long urlRequestAdapter);
543
544 // Explicit class to work around JNI-generator generics confusion.
545 private static class HeadersMap extends HashMap<String, List<String>> {
49 } 546 }
50 } 547 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698