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

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

Powered by Google App Engine
This is Rietveld 408576698