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

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

Powered by Google App Engine
This is Rietveld 408576698