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

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, 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
7 /** 20 /**
8 * UrlRequest using Chromium HTTP stack implementation. 21 * UrlRequest using Chromium HTTP stack implementation.
9 */ 22 */
10 public class CronetUrlRequest implements UrlRequest { 23 @JNINamespace("cronet")
24 final class CronetUrlRequest implements UrlRequest {
25 /** Native adapter object, owned by UrlRequest. */
26 private long mUrlRequestAdapter;
27 private final CronetUrlRequestContext mRequestContext;
28 private final List<String> mUrlChain = new ArrayList<String>();
29 private final int mPriority;
30 private final UrlRequestListener mListener;
31 private final Executor mExecutor;
32 private NativeResponseInfo mResponseInfo;
33 private boolean mCanceled = false;
34 private OnDataReceivedRunnable mOnDataReceivedTask;
35
36 final class OnDataReceivedRunnable implements Runnable {
37 ByteBuffer mByteBuffer;
38 public void run() {
39 try {
40 mListener.onDataReceived(CronetUrlRequest.this,
41 mResponseInfo, mByteBuffer);
42 mByteBuffer = null;
43 if (!isCanceled())
44 nativeReceiveData(mUrlRequestAdapter);
45 } catch (Exception e) {
46 onCalledByNativeException(e);
47 }
48 }
49 }
50
51 final class NativeResponseInfo implements ExtendedResponseInfo {
52 private final String[] mUrlChain;
53 private final int mHttpStatusCode;
54 private final ResponseHeadersMap mAllHeaders = new ResponseHeadersMap();
55 private final boolean mWasCached;
56 private final String mNegotiatedProtocol;
57 private long mTotalReceivedBytes = 0;
58
59 NativeResponseInfo(String[] urlChain, int httpStatusCode, boolean wasCac hed,
60 String negotiatedProtocol) {
61 mUrlChain = urlChain;
62 mHttpStatusCode = httpStatusCode;
63 mWasCached = wasCached;
64 mNegotiatedProtocol = negotiatedProtocol;
65 }
66
67 @Override
68 public String getUrl() {
69 return mUrlChain[mUrlChain.length - 1];
70 }
71
72 @Override
73 public String[] getUrlChain() {
74 return mUrlChain;
75 }
76
77 @Override
78 public int getHttpStatusCode() {
79 return mHttpStatusCode;
80 }
81
82 @Override
83 public Map<String, List<String>> getAllHeaders() {
84 return mAllHeaders;
85 }
86
87 @Override
88 public boolean wasCached() {
89 return mWasCached;
90 }
91
92 @Override
93 public String getNegotiatedProtocol() {
94 return mNegotiatedProtocol;
95 }
96
97 @Override
98 public long getTotalReceivedBytes() {
99 return mTotalReceivedBytes;
100 }
101 };
102
103 CronetUrlRequest(CronetUrlRequestContext requestContext,
104 String url, int priority,
105 UrlRequestListener listener,
106 Executor executor) {
107 if (requestContext == null) {
108 throw new NullPointerException("Context is required");
109 }
110 if (url == null) {
111 throw new NullPointerException("URL is required");
112 }
113 if (listener == null) {
114 throw new NullPointerException("Listener is required");
115 }
116 if (executor == null) {
117 throw new NullPointerException("Executor is required");
118 }
119
120 mRequestContext = requestContext;
121 mUrlChain.add(url);
122 mPriority = convertRequestPriority(priority);
123 mUrlRequestAdapter = nativeCreateRequestAdapter(
124 mRequestContext.getCronetUrlRequestContextAdapter(),
125 url,
126 mPriority);
127 mListener = listener;
128 mExecutor = executor;
129 }
130
11 @Override 131 @Override
12 public void setHttpMethod(String method) { 132 public void setHttpMethod(String method) {
mmenke 2014/10/06 18:28:45 Should we throw some exception on these setters if
mef 2014/10/07 00:45:18 Done.
13 133 if (method == null) {
134 throw new NullPointerException("method is required");
135 }
136 nativeSetHttpMethod(mUrlRequestAdapter, method);
14 } 137 }
15 138
16 @Override 139 @Override
17 public void addHeader(String header, String value) { 140 public void addHeader(String header, String value) {
18 141 if (header == null || value == null) {
142 throw new NullPointerException("Invalid header");
143 }
144 nativeAddHeader(mUrlRequestAdapter, header, value);
19 } 145 }
20 146
21 @Override 147 @Override
22 public void start(UrlRequestListener listener) { 148 public void start() {
23 149 nativeStart(mUrlRequestAdapter);
24 } 150 }
25 151
26 @Override 152 @Override
27 public void cancel() { 153 public void cancel() {
mmenke 2014/10/06 18:28:44 I suggest two cancels: One for errors, which call
mef 2014/10/17 20:19:41 Done.
28 154 nativeCancel(mUrlRequestAdapter);
29 } 155 }
30 156
31 @Override 157 @Override
32 public boolean isCanceled() { 158 public boolean isCanceled() {
33 return false; 159 return mCanceled;
mmenke 2014/10/06 18:28:44 mCanceled should only be read and set on one threa
mef 2014/10/07 00:45:18 Done. Moved setter to executor thread.
34 } 160 }
35 161
36 @Override 162 @Override
37 public void pause() { 163 public void pause() {
38 164 throw new UnsupportedOperationException("Not implemented yet");
39 } 165 }
40 166
41 @Override 167 @Override
42 public boolean isPaused() { 168 public boolean isPaused() {
43 return false; 169 return false;
44 } 170 }
45 171
46 @Override 172 @Override
47 public void resume() { 173 public void resume() {
48 174 throw new UnsupportedOperationException("Not implemented yet");
49 } 175 }
176
177 private static int convertRequestPriority(int priority) {
178 switch (priority) {
179 case REQUEST_PRIORITY_IDLE:
180 return ChromiumUrlRequestPriority.IDLE;
181 case REQUEST_PRIORITY_LOWEST:
182 return ChromiumUrlRequestPriority.LOWEST;
183 case REQUEST_PRIORITY_LOW:
184 return ChromiumUrlRequestPriority.LOW;
185 case REQUEST_PRIORITY_MEDIUM:
186 return ChromiumUrlRequestPriority.MEDIUM;
187 case REQUEST_PRIORITY_HIGHEST:
188 return ChromiumUrlRequestPriority.HIGHEST;
189 default:
190 return ChromiumUrlRequestPriority.MEDIUM;
191 }
192 }
193
194 private NativeResponseInfo prepareResponseInfo() {
195 NativeResponseInfo responseInfo = new NativeResponseInfo(
196 mUrlChain.toArray(new String[mUrlChain.size()]),
197 nativeGetHttpStatusCode(mUrlRequestAdapter),
198 nativeGetWasCached(mUrlRequestAdapter),
199 nativeGetNegotiatedProtocol(mUrlRequestAdapter));
200 nativeGetAllHeaders(mUrlRequestAdapter, responseInfo.mAllHeaders);
201 return responseInfo;
202 }
203
204 // Private methods called by native library.
205
206 /**
207 * If @CalledByNative method throws an exception, request gets cancelled
208 * and exception could be retrieved from request using getException().
209 */
210 private void onCalledByNativeException(Exception e) {
211 UrlRequestException requestError = new UrlRequestException(
212 "CalledByNative method has thrown an exception", e);
213 Log.e(CronetUrlRequestContext.LOG_TAG,
214 "Exception in CalledByNative method", e);
215 try {
216 mListener.onError(this, mResponseInfo, requestError);
217 cancel();
218 } catch (Exception cancelException) {
219 Log.e(CronetUrlRequestContext.LOG_TAG,
220 "Exception trying to cancel request", cancelException);
221 }
222 }
223
224 /**
225 * Called before following redirects. The redirect will automatically be
226 * followed, unless the request is paused or cancelled during this
227 * callback. If the redirect response has a body, it will be ignored.
228 * This will only be called between start and onResponseStarted.
229 *
230 * @param info Response information.
231 * @param newLocation Location where request is redirected.
232 */
233 @SuppressWarnings("unused")
234 @CalledByNative
235 private void onRedirect(final String newLocation) {
236 mUrlChain.add(newLocation);
237 final NativeResponseInfo responseInfo = prepareResponseInfo();
238 Runnable task = new Runnable() {
239 public void run() {
240 try {
241 mListener.onRedirect(CronetUrlRequest.this, responseInfo,
242 new URL(newLocation));
243 if (!isCanceled())
244 nativeFollowDeferredRedirect(mUrlRequestAdapter);
245 } catch (Exception e) {
246 onCalledByNativeException(e);
247 }
248 }
249 };
250 mExecutor.execute(task);
251 }
252
253 /**
254 * Called when the final set of headers, after all redirects,
255 * is received. Can only be called once for each request.
256 *
257 * @param info Response information.
258 */
259 @SuppressWarnings("unused")
260 @CalledByNative
261 private void onResponseStarted() {
262 mResponseInfo = prepareResponseInfo();
263 Runnable task = new Runnable() {
264 public void run() {
265 try {
266 mListener.onResponseStarted(CronetUrlRequest.this,
267 mResponseInfo);
268 if (!isCanceled())
269 nativeReceiveData(mUrlRequestAdapter);
270 } catch (Exception e) {
271 onCalledByNativeException(e);
272 }
273 }
274 };
275 mExecutor.execute(task);
276 }
277
278 /**
279 * Called whenever data is received. The ByteBuffer remains
280 * valid only until listener callback. Or if the callback
281 * pauses the request, it remains valid until the request is resumed.
282 * Cancelling the request also invalidates the buffer.
283 *
284 * @param byteBuffer Received data.
285 */
286 @SuppressWarnings("unused")
287 @CalledByNative
288 private void onDataReceived(final ByteBuffer byteBuffer) {
289 if (mOnDataReceivedTask == null)
290 mOnDataReceivedTask = new OnDataReceivedRunnable();
mmenke 2014/10/06 18:28:45 Do we save much by reusing this task? Seems clean
mef 2014/10/07 00:45:18 I agree, but clm@ has pointed out possible memory
291 mOnDataReceivedTask.mByteBuffer = byteBuffer;
mmenke 2014/10/06 18:28:45 Should we check if this is NULL? If you opt not t
mef 2014/10/07 00:45:18 Umm, could you elaborate, what do you mean by 'thi
292 mExecutor.execute(mOnDataReceivedTask);
293 }
294
295 /**
296 * Called when request is complete, no callbacks will be called afterwards.
297 */
298 @SuppressWarnings("unused")
299 @CalledByNative
300 private void onComplete(boolean canceled) {
301 mCanceled = canceled;
302 mResponseInfo.mTotalReceivedBytes =
303 nativeGetTotalReceivedBytes(mUrlRequestAdapter);
304 Runnable task = new Runnable() {
305 public void run() {
306 try {
307 mListener.onComplete(CronetUrlRequest.this, mResponseInfo);
308 nativeDestroyRequestAdapter(mUrlRequestAdapter);
309 } catch (Exception e) {
310 onCalledByNativeException(e);
311 }
312 }
313 };
314 mExecutor.execute(task);
315 }
316
317 /**
318 * Called when error has occured, no callbacks will be called afterwards.
319 */
320 @SuppressWarnings("unused")
321 @CalledByNative
322 private void onError(int error) {
323 Runnable task = new Runnable() {
324 public void run() {
325 try {
326 UrlRequestException requestError = new UrlRequestException(
327 "Exception in CronetUrlRequest ", null);
328 mListener.onError(CronetUrlRequest.this,
329 mResponseInfo,
330 requestError);
331 nativeDestroyRequestAdapter(mUrlRequestAdapter);
332 } catch (Exception e) {
333 onCalledByNativeException(e);
334 }
335 }
336 };
337 mExecutor.execute(task);
338 }
339
340 /**
341 * Appends header |name| with value |value| to |headersMap|.
342 */
343 @SuppressWarnings("unused")
344 @CalledByNative
345 private void onAppendResponseHeader(ResponseHeadersMap headersMap,
346 String name, String value) {
347 try {
348 if (!headersMap.containsKey(name)) {
349 headersMap.put(name, new ArrayList<String>());
350 }
351 headersMap.get(name).add(value);
352 } catch (Exception e) {
353 onCalledByNativeException(e);
354 }
355 }
356
357 // Native methods are implemented in cronet_url_request.cc.
358
359 private native long nativeCreateRequestAdapter(
360 long urlRequestContextAdapter, String url, int priority);
361
362 private native void nativeAddHeader(long urlRequestAdapter, String name,
363 String value);
364
365 private native void nativeSetHttpMethod(long urlRequestAdapter,
366 String method);
367
368 private native void nativeStart(long urlRequestAdapter);
369
370 private native void nativeCancel(long urlRequestAdapter);
371
372 private native void nativeDestroyRequestAdapter(long urlRequestAdapter);
373
374 private native void nativeFollowDeferredRedirect(long urlRequestAdapter);
375
376 private native void nativeReceiveData(long urlRequestAdapter);
377
378 // TODO(mef): Remove unused methods (if any).
379
380 private native int nativeGetHttpStatusCode(long urlRequestAdapter);
381
382 private native String nativeGetContentType(long urlRequestAdapter);
383
384 private native long nativeGetContentLength(long urlRequestAdapter);
385
386 private native void nativeGetAllHeaders(long urlRequestAdapter,
387 ResponseHeadersMap headers);
388
389 private native String nativeGetNegotiatedProtocol(long urlRequestAdapter);
390
391 private native boolean nativeGetWasCached(long urlRequestAdapter);
392
393 private native long nativeGetTotalReceivedBytes(long urlRequestAdapter);
394
395 // Explicit class to work around JNI-generator generics confusion.
396 private class ResponseHeadersMap extends HashMap<String, List<String>> {
397 }
398
399
mmenke 2014/10/06 18:28:44 nit: Remove extra blank lines.
mef 2014/10/07 00:45:18 Done.
50 } 400 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698