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

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 Helen's comments, add CronetUrlRequestContextTest. 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 destroyRequestAdapter();
185 }
29 } 186 }
30 187
31 @Override 188 @Override
32 public boolean isCanceled() { 189 public boolean isCanceled() {
33 return false; 190 return mCanceled.get();
34 } 191 }
35 192
36 @Override 193 @Override
37 public void pause() { 194 public void pause() {
38 195 throw new UnsupportedOperationException("Not implemented yet");
39 } 196 }
40 197
41 @Override 198 @Override
42 public boolean isPaused() { 199 public boolean isPaused() {
43 return false; 200 return false;
44 } 201 }
45 202
46 @Override 203 @Override
47 public void resume() { 204 public void resume() {
48 205 throw new UnsupportedOperationException("Not implemented yet");
206 }
207
208 /**
209 * Post task to application Executor or Looper. Used for Listener callbacks
210 * and other tasks that should not be executed on network thread.
211 */
212 private void postAppTask(Runnable task) {
213 mExecutor.execute(task);
214 }
215
216 private static int convertRequestPriority(int priority) {
217 switch (priority) {
218 case REQUEST_PRIORITY_IDLE:
219 return ChromiumUrlRequestPriority.IDLE;
220 case REQUEST_PRIORITY_LOWEST:
221 return ChromiumUrlRequestPriority.LOWEST;
222 case REQUEST_PRIORITY_LOW:
223 return ChromiumUrlRequestPriority.LOW;
224 case REQUEST_PRIORITY_MEDIUM:
225 return ChromiumUrlRequestPriority.MEDIUM;
226 case REQUEST_PRIORITY_HIGHEST:
227 return ChromiumUrlRequestPriority.HIGHEST;
228 default:
229 return ChromiumUrlRequestPriority.MEDIUM;
230 }
231 }
232
233 private NativeResponseInfo prepareResponseInfo(int httpStatusCode) {
234 long urlRequestAdapter = mUrlRequestAdapter.get();
235 NativeResponseInfo responseInfo = new NativeResponseInfo(
236 mUrlChain.toArray(new String[mUrlChain.size()]),
237 httpStatusCode,
238 nativeGetWasCached(urlRequestAdapter),
239 nativeGetNegotiatedProtocol(urlRequestAdapter));
240 nativePopulateResponseHeaders(urlRequestAdapter,
241 responseInfo.mAllHeaders);
242 return responseInfo;
243 }
244
245 private void checkNotStartedOrDone() {
246 if (mStarted) {
247 throw new IllegalStateException("Request is already started.");
248 }
249 if (mUrlRequestAdapter.get() == 0) {
250 throw new IllegalStateException("Request is already destroyed.");
251 }
252 }
253
254 private void destroyRequestAdapter() {
255 long urlRequestAdapter = mUrlRequestAdapter.getAndSet(0);
256 if (urlRequestAdapter != 0) {
257 nativeDestroyRequestAdapter(urlRequestAdapter);
mmenke 2014/10/28 18:07:52 Per previous discussions, this isn't threadsafe.
mef 2014/10/28 21:04:25 Done.
258 mRequestContext.onRequestDestroyed(this);
259 }
260 }
261
262 /**
263 * If @CalledByNative method throws an exception, request gets cancelled
264 * and exception could be retrieved from request using getException().
265 */
266 private void onCalledByNativeException(Exception e) {
267 UrlRequestException requestError = new UrlRequestException(
268 "CalledByNative method has thrown an exception", e);
269 Log.e(CronetUrlRequestContext.LOG_TAG,
270 "Exception in CalledByNative method", e);
271 try {
272 cancel();
273 mListener.onError(this, mResponseInfo, requestError);
274 } catch (Exception cancelException) {
275 Log.e(CronetUrlRequestContext.LOG_TAG,
276 "Exception trying to cancel request", cancelException);
277 }
278 }
279
280 ////////////////////////////////////////////////
281 // Private methods called by the native code.
282 ////////////////////////////////////////////////
283
284 /**
285 * Called before following redirects. The redirect will automatically be
286 * followed, unless the request is paused or cancelled during this
287 * callback. If the redirect response has a body, it will be ignored.
288 * This will only be called between start and onResponseStarted.
289 *
290 * @param newLocation Location where request is redirected.
291 * @param httpStatusCode from redirect response
292 */
293 @SuppressWarnings("unused")
294 @CalledByNative
295 private void onRedirect(final String newLocation, int httpStatusCode) {
296 mUrlChain.add(newLocation);
297 final NativeResponseInfo responseInfo =
298 prepareResponseInfo(httpStatusCode);
299 Runnable task = new Runnable() {
300 public void run() {
301 if (isCanceled()) {
302 return;
303 }
304 try {
305 mListener.onRedirect(CronetUrlRequest.this, responseInfo,
306 new URL(newLocation));
307 nativeFollowDeferredRedirect(mUrlRequestAdapter.get());
308 } catch (Exception e) {
309 onCalledByNativeException(e);
310 }
311 }
312 };
313 postAppTask(task);
314 }
315
316 /**
317 * Called when the final set of headers, after all redirects,
318 * is received. Can only be called once for each request.
319 */
320 @SuppressWarnings("unused")
321 @CalledByNative
322 private void onResponseStarted(int httpStatusCode) {
323 mResponseInfo = prepareResponseInfo(httpStatusCode);
324 Runnable task = new Runnable() {
325 public void run() {
326 if (isCanceled()) {
327 return;
328 }
329 try {
330 mListener.onResponseStarted(CronetUrlRequest.this,
331 mResponseInfo);
332 nativeReceiveData(mUrlRequestAdapter.get());
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 complete, no callbacks will be called afterwards.
361 */
362 @SuppressWarnings("unused")
363 @CalledByNative
364 private void onComplete() {
365 final NativeExtendedResponseInfo extendedResponseInfo =
366 new NativeExtendedResponseInfo(mResponseInfo,
367 nativeGetTotalReceivedBytes(mUrlRequestAdapter.get()));
368 Runnable task = new Runnable() {
369 public void run() {
370 if (isCanceled()) {
371 return;
372 }
373 try {
374 mListener.onComplete(CronetUrlRequest.this,
375 extendedResponseInfo);
376 } catch (Exception e) {
377 Log.e(CronetUrlRequestContext.LOG_TAG,
378 "Exception in onComplete method", e);
379 }
380 destroyRequestAdapter();
381 }
382 };
383 postAppTask(task);
384 }
385
386 /**
387 * Called when error has occured, no callbacks will be called afterwards.
388 *
389 * @param nativeError native net error code.
390 * @param errorString textual representation of the error code.
391 */
392 @SuppressWarnings("unused")
393 @CalledByNative
394 private void onError(final int nativeError, final String errorString) {
395 Runnable task = new Runnable() {
396 public void run() {
397 if (isCanceled()) {
398 return;
399 }
400 try {
401 UrlRequestException requestError = new UrlRequestException(
402 "Exception in CronetUrlRequest: " + errorString,
403 nativeError);
404 mListener.onError(CronetUrlRequest.this,
405 mResponseInfo,
406 requestError);
407 destroyRequestAdapter();
408 } catch (Exception e) {
409 onCalledByNativeException(e);
410 }
411 }
412 };
413 postAppTask(task);
414 }
415
416 /**
417 * Appends header |name| with value |value| to |headersMap|.
418 */
419 @SuppressWarnings("unused")
420 @CalledByNative
421 private void onAppendResponseHeader(ResponseHeadersMap headersMap,
422 String name, String value) {
423 try {
424 if (!headersMap.containsKey(name)) {
425 headersMap.put(name, new ArrayList<String>());
426 }
427 headersMap.get(name).add(value);
428 } catch (Exception e) {
429 onCalledByNativeException(e);
430 }
431 }
432
433 // Native methods are implemented in cronet_url_request.cc.
434
435 private native long nativeCreateRequestAdapter(
436 long urlRequestContextAdapter, String url, int priority);
437
438 private native boolean nativeAddHeader(long urlRequestAdapter, String name,
439 String value);
440
441 private native void nativeSetHttpMethod(long urlRequestAdapter,
442 String method);
443
444 private native void nativeStart(long urlRequestAdapter);
445
446 private native void nativeDestroyRequestAdapter(long urlRequestAdapter);
447
448 private native void nativeFollowDeferredRedirect(long urlRequestAdapter);
449
450 private native void nativeReceiveData(long urlRequestAdapter);
451
452 private native void nativePopulateResponseHeaders(long urlRequestAdapter,
453 ResponseHeadersMap headers);
454
455 private native String nativeGetNegotiatedProtocol(long urlRequestAdapter);
456
457 private native boolean nativeGetWasCached(long urlRequestAdapter);
458
459 private native long nativeGetTotalReceivedBytes(long urlRequestAdapter);
460
461 // Explicit class to work around JNI-generator generics confusion.
462 private static class ResponseHeadersMap extends
463 HashMap<String, List<String>> {
49 } 464 }
50 } 465 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698