OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package org.chromium.net; | |
6 | |
7 import android.util.Log; | |
xunjieli
2015/12/18 19:11:12
we should avoid using android.util.Log in new file
mef
2015/12/29 20:36:54
Done.
| |
8 | |
9 import org.chromium.base.VisibleForTesting; | |
10 import org.chromium.base.annotations.CalledByNative; | |
11 import org.chromium.base.annotations.JNINamespace; | |
12 import org.chromium.base.annotations.NativeClassQualifiedName; | |
13 | |
14 import java.nio.ByteBuffer; | |
15 import java.util.AbstractMap; | |
16 import java.util.ArrayList; | |
17 import java.util.List; | |
18 import java.util.Map; | |
19 import java.util.concurrent.Executor; | |
20 import java.util.concurrent.RejectedExecutionException; | |
21 | |
22 import javax.annotation.concurrent.GuardedBy; | |
23 | |
24 /** | |
25 * BidirectionalStream implementation using Chromium network stack. | |
26 * All @CallByNative methods are called on native network thread | |
27 * and post tasks with callback calls onto Executor. Upon return from callback n ative | |
28 * stream is called on executor thread and posts native tasks to native network thread. | |
29 */ | |
30 @JNINamespace("cronet") | |
31 class CronetBidirectionalStream extends BidirectionalStream { | |
32 /** | |
33 * States of BidirectionalStream are tracked in mStreamState and mWriteState . | |
34 * The write state is separated out as it changes independently of the strea m state. | |
35 * There is one initial state - STATE_NOT_STARTED. There is one final state - STATE_SUCCESS, | |
36 * reached after STATE_READING_DONE and STATE_WRITING_DONE. There are 2 exce ption states - | |
37 * STATE_CANCELED and STATE_ERROR, which can be reached from any other state except | |
38 * STATE_SUCCESS. | |
39 */ | |
40 /* Initial state, stream not started. */ | |
41 private static final int STATE_NOT_STARTED = 0; | |
42 /* Stream started, request headers are sent. */ | |
43 private static final int STATE_STARTED = 1; | |
44 /* Waiting for {@code read()} to be called. */ | |
45 private static final int STATE_WAITING_ON_READ = 2; | |
46 /* Reading from the remote, {@code onReadCompleted()} callback will be calle d when done. */ | |
47 private static final int STATE_READING = 3; | |
48 /* There is no more data to read and stream is half-closed by the remote sid e. */ | |
49 private static final int STATE_READING_DONE = 4; | |
50 /* Stream is canceled. */ | |
51 private static final int STATE_CANCELED = 5; | |
52 /* Error has occured, stream is closed. */ | |
53 private static final int STATE_ERROR = 6; | |
54 /* Reading and writing is done, and the stream is closed successfully. */ | |
55 private static final int STATE_SUCCESS = 7; | |
56 /* Waiting for {@code write()} to be called. */ | |
57 private static final int STATE_WAITING_ON_WRITE = 10; | |
58 /* Writing to the remote, {@code onWriteCompleted()} callback will be called when done. */ | |
59 private static final int STATE_WRITING = 11; | |
60 /* Writing the last frame, so {@code STATE_WRITING_DONE} will be set upon co mpletion. */ | |
61 private static final int STATE_WRITING_END_OF_STREAM = 12; | |
62 /* There is no more data to write and stream is half-closed by the local sid e. */ | |
63 private static final int STATE_WRITING_DONE = 13; | |
64 | |
65 /* | |
66 * Synchronize access to mNativeStream, mStreamState and mWriteState. | |
67 */ | |
68 private final Object mNativeStreamLock = new Object(); | |
69 private final CronetUrlRequestContext mRequestContext; | |
70 private final Executor mExecutor; | |
71 private final Callback mCallback; | |
72 private final String mInitialUrl; | |
73 private final String mInitialMethod; | |
74 private final ArrayList<Map.Entry<String, String>> mRequestHeaders; | |
75 | |
76 /* Native BidirectionalStream object, owned by CronetBidirectionalStream. */ | |
77 @GuardedBy("mNativeStreamLock") private long mNativeStream; | |
78 | |
79 /** | |
80 * Stream state is tracking stream and reading flow. | |
81 * NOT_STARTED -> STARTED -> WAITING_ON_READ -> READING -> WAITING_ON_READ - > | |
82 * READING -> READING_DONE -> SUCCESS | |
83 */ | |
84 @GuardedBy("mNativeStreamLock") private int mStreamState = STATE_NOT_STARTED ; | |
xunjieli
2015/12/18 19:49:25
nit: mReadState?
mef
2015/12/29 20:36:54
I'm not sure. This is both read and common stream
| |
85 | |
86 /** | |
87 * Write state is tracking writing flow. | |
88 * NOT_STARTED -> WAITING_ON_WRITE -> WRITING -> WAITING_ON_WRITE -> | |
89 * WRITING_END_OF_STREAM -> WRITING_DONE -> SUCCESS | |
90 */ | |
91 @GuardedBy("mNativeStreamLock") private int mWriteState = STATE_NOT_STARTED; | |
92 | |
93 private UrlResponseInfo mResponseInfo; | |
94 | |
95 /* | |
96 * OnReadCompleted callback is repeatedly invoked when each read is complete d, so it | |
97 * is cached as a member variable. | |
98 */ | |
99 private OnReadCompletedRunnable mOnReadCompletedTask; | |
100 | |
101 /* | |
102 * OnWriteCompleted callback is repeatedly invoked when each write is comple ted, so it | |
103 * is cached as a member variable. | |
104 */ | |
105 private OnWriteCompletedRunnable mOnWriteCompletedTask; | |
106 | |
107 private Runnable mOnDestroyedCallbackForTests; | |
xunjieli
2015/12/18 19:11:11
nit: maybe using ForTesting. I searched in cronet/
mef
2015/12/29 20:36:54
Done.
| |
108 | |
109 private final class OnReadCompletedRunnable implements Runnable { | |
110 ByteBuffer mByteBuffer; | |
111 boolean mEndOfStream; | |
xunjieli
2015/12/18 19:11:12
nit: need documentation on these two fields.
mef
2015/12/29 20:36:54
Done.
| |
112 | |
113 @Override | |
114 public void run() { | |
115 if (isDone()) { | |
116 return; | |
117 } | |
118 try { | |
119 synchronized (mNativeStreamLock) { | |
120 if (mNativeStream == 0) { | |
121 return; | |
122 } | |
123 if (mEndOfStream) { | |
124 mStreamState = STATE_READING_DONE; | |
125 if (maybeSucceeded()) return; | |
126 } else { | |
127 mStreamState = STATE_WAITING_ON_READ; | |
128 } | |
129 } | |
130 // Null out mByteBuffer, out of paranoia. Has to be done before | |
131 // mCallback call, to avoid any race when there are multiple | |
132 // executor threads. | |
133 ByteBuffer buffer = mByteBuffer; | |
134 mByteBuffer = null; | |
135 mCallback.onReadCompleted(CronetBidirectionalStream.this, mRespo nseInfo, buffer); | |
136 } catch (Exception e) { | |
137 onCallbackException(e); | |
138 } | |
139 } | |
140 } | |
141 | |
142 private final class OnWriteCompletedRunnable implements Runnable { | |
143 ByteBuffer mByteBuffer; | |
xunjieli
2015/12/18 19:11:12
nit: need doc on this mByteBuffer field.
mef
2015/12/29 20:36:54
Done.
| |
144 | |
145 @Override | |
146 public void run() { | |
147 if (isDone()) { | |
148 return; | |
149 } | |
150 try { | |
151 synchronized (mNativeStreamLock) { | |
152 if (mNativeStream == 0) { | |
153 return; | |
154 } | |
155 if (mWriteState == STATE_WRITING_END_OF_STREAM) { | |
156 mWriteState = STATE_WRITING_DONE; | |
157 if (maybeSucceeded()) return; | |
158 } else { | |
159 mWriteState = STATE_WAITING_ON_WRITE; | |
160 } | |
161 } | |
162 // Null out mByteBuffer, out of paranoia. Has to be done before | |
163 // mCallback call, to avoid any race when there are multiple | |
164 // executor threads. | |
165 ByteBuffer buffer = mByteBuffer; | |
166 mByteBuffer = null; | |
167 mCallback.onWriteCompleted(CronetBidirectionalStream.this, mResp onseInfo, buffer); | |
168 } catch (Exception e) { | |
169 onCallbackException(e); | |
170 } | |
171 } | |
172 } | |
173 | |
174 @GuardedBy("nativeStreamLock") | |
175 private boolean maybeSucceeded() { | |
176 if (mStreamState != STATE_READING_DONE || mWriteState != STATE_WRITING_D ONE) { | |
177 return false; | |
178 } | |
179 | |
180 mStreamState = STATE_SUCCESS; | |
181 Runnable task = new Runnable() { | |
182 public void run() { | |
183 synchronized (mNativeStreamLock) { | |
184 if (isDone()) { | |
185 return; | |
186 } | |
187 // Destroy native stream first, so request context could be shut | |
188 // down from the listener. | |
189 destroyNativeStream(false); | |
190 } | |
191 try { | |
192 mCallback.onSucceeded(CronetBidirectionalStream.this, mRespo nseInfo); | |
193 } catch (Exception e) { | |
194 Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in onSucce eded method", e); | |
195 } | |
196 } | |
197 }; | |
198 postTaskToExecutor(task); | |
199 return true; | |
200 } | |
201 | |
202 private static boolean doesMethodAllowWriteData(String methodName) { | |
203 return !methodName.equals("GET") && !methodName.equals("HEAD"); | |
204 } | |
205 | |
206 CronetBidirectionalStream(CronetUrlRequestContext requestContext, long urlRe questContextAdapter, | |
xunjieli
2015/12/18 19:11:11
nit: move constructor to the top. At least before
mef
2015/12/29 20:36:54
Done.
| |
207 String url, Callback callback, Executor executor, String httpMethod, | |
208 List<Map.Entry<String, String>> requestHeaders) { | |
209 mRequestContext = requestContext; | |
210 mInitialUrl = url; | |
211 mCallback = callback; | |
212 mExecutor = executor; | |
213 mInitialMethod = httpMethod; | |
214 mRequestHeaders = new ArrayList<Map.Entry<String, String>>(requestHeader s); | |
xunjieli
2015/12/18 19:11:11
Is this a deep copy?
mef
2015/12/29 20:36:54
It constructs a list containing elements of passed
| |
215 } | |
216 | |
217 @Override | |
218 public void start() { | |
219 synchronized (mNativeStreamLock) { | |
220 if (mStreamState != STATE_NOT_STARTED) { | |
221 throw new IllegalStateException("Stream is already started."); | |
222 } | |
223 try { | |
224 mNativeStream = nativeCreateBidirectionalStream( | |
225 mRequestContext.getUrlRequestContextAdapter()); | |
226 mRequestContext.onRequestStarted(); | |
227 String headers[] = stringsFromHeaderList(mRequestHeaders); | |
228 // Non-zero startResult means an argument error. | |
229 int startResult = nativeStart(mNativeStream, mInitialUrl, mIniti alMethod, headers, | |
230 !doesMethodAllowWriteData(mInitialMethod)); | |
231 if (startResult == -1) { | |
232 throw new IllegalArgumentException("Invalid http method " + mInitialMethod); | |
233 } | |
234 if (startResult > 0) { | |
235 int headerPos = startResult - 1; | |
236 throw new IllegalArgumentException( | |
237 "Invalid header " + headers[headerPos] + "=" + heade rs[headerPos + 1]); | |
238 } | |
239 mStreamState = STATE_STARTED; | |
240 } catch (RuntimeException e) { | |
241 // If there's an exception, cleanup and then throw the | |
242 // exception to the caller. | |
243 destroyNativeStream(false); | |
244 throw e; | |
245 } | |
246 } | |
247 } | |
248 | |
249 @Override | |
250 public void read(ByteBuffer buffer) { | |
251 synchronized (mNativeStreamLock) { | |
252 if (!buffer.hasRemaining()) { | |
253 throw new IllegalArgumentException("ByteBuffer is already full." ); | |
254 } | |
255 if (mStreamState != STATE_WAITING_ON_READ) { | |
256 throw new IllegalStateException("Unexpected read attempt."); | |
257 } | |
258 if (isDone()) { | |
259 return; | |
260 } | |
261 mStreamState = STATE_READING; | |
262 if (!nativeReadData(mNativeStream, buffer, buffer.position(), buffer .limit())) { | |
263 // Still waiting on read. This is just to have consistent | |
264 // behavior with the other error cases. | |
265 mStreamState = STATE_WAITING_ON_READ; | |
266 // Since accessing byteBuffer's memory failed, it's presumably | |
267 // not a direct ByteBuffer. | |
268 throw new IllegalArgumentException("byteBuffer must be a direct ByteBuffer."); | |
269 } | |
270 } | |
271 } | |
272 | |
273 @Override | |
274 public void write(ByteBuffer buffer, boolean endOfStream) { | |
275 synchronized (mNativeStreamLock) { | |
276 if (!buffer.hasRemaining() && !endOfStream) { | |
277 throw new IllegalArgumentException("Empty buffer before end of s tream."); | |
278 } | |
279 if (mWriteState != STATE_WAITING_ON_WRITE) { | |
280 throw new IllegalStateException("Unexpected write attempt."); | |
281 } | |
282 if (isDone()) { | |
283 return; | |
284 } | |
285 mWriteState = endOfStream ? STATE_WRITING_END_OF_STREAM : STATE_WRIT ING; | |
286 if (!nativeWriteData( | |
287 mNativeStream, buffer, buffer.position(), buffer.limit() , endOfStream)) { | |
288 // Still waiting on write. This is just to have consistent | |
289 // behavior with the other error cases. | |
290 mWriteState = STATE_WAITING_ON_WRITE; | |
291 // Since accessing byteBuffer's memory failed, it's presumably | |
292 // not a direct ByteBuffer. | |
293 throw new IllegalArgumentException("byteBuffer must be a direct ByteBuffer."); | |
294 } | |
295 } | |
296 } | |
297 | |
298 @Override | |
299 public void ping(PingCallback callback, Executor executor) { | |
300 // TODO(mef): May be last thing to be implemented on Android. | |
xunjieli
2015/12/18 19:11:11
nit: throw UnsupportedOperation with a not yet sup
mef
2015/12/29 20:36:55
Done.
| |
301 } | |
302 | |
303 @Override | |
304 public void windowUpdate(int windowSizeIncrement) { | |
305 // TODO(mef): Understand the needs and semantics of this method. | |
306 } | |
307 | |
308 /** | |
309 * Cancels the stream. Can be called at any time after {@link #start}. | |
310 * {@link Callback#onCanceled} will be invoked when cancelation | |
311 * is complete and no further callback methods will be invoked. If the | |
312 * stream has completed or has not started, calling {@code cancel()} has no | |
313 * effect and {@code onCanceled} will not be invoked. If the | |
314 * {@link Executor} passed in during {@code BidirectionalStream} constructio n runs | |
315 * tasks on a single thread, and {@code cancel()} is called on that thread, | |
316 * no listener methods (besides {@code onCanceled()}) will be invoked after | |
317 * {@code cancel()} is called. Otherwise, at most one callback method may be | |
318 * invoked after {@code cancel()} has completed. | |
319 */ | |
320 public void cancel() { | |
xunjieli
2015/12/18 19:11:11
nit: add @override, and remove documentation.
mef
2015/12/29 20:36:54
Done.
| |
321 synchronized (mNativeStreamLock) { | |
322 if (isDone() || mStreamState == STATE_NOT_STARTED) { | |
323 return; | |
324 } | |
325 mStreamState = STATE_CANCELED; | |
326 destroyNativeStream(true); | |
327 } | |
328 } | |
329 | |
330 @Override | |
331 public boolean isDone() { | |
332 synchronized (mNativeStreamLock) { | |
333 return mStreamState != STATE_NOT_STARTED && mNativeStream == 0; | |
334 } | |
335 } | |
336 | |
337 @SuppressWarnings("unused") | |
338 @CalledByNative | |
339 private void onRequestHeadersSent() { | |
340 Runnable task = new Runnable() { | |
341 public void run() { | |
342 synchronized (mNativeStreamLock) { | |
343 if (isDone()) { | |
344 return; | |
345 } | |
346 if (doesMethodAllowWriteData(mInitialMethod)) { | |
347 mWriteState = STATE_WAITING_ON_WRITE; | |
348 } else { | |
349 mWriteState = STATE_WRITING_DONE; | |
350 } | |
351 } | |
352 | |
353 try { | |
354 mCallback.onRequestHeadersSent(CronetBidirectionalStream.thi s); | |
355 } catch (Exception e) { | |
356 onCallbackException(e); | |
357 } | |
358 } | |
359 }; | |
360 postTaskToExecutor(task); | |
361 } | |
362 | |
363 /** | |
364 * Called when the final set of headers, after all redirects, | |
365 * is received. Can only be called once for each stream. | |
366 */ | |
367 @SuppressWarnings("unused") | |
368 @CalledByNative | |
369 private void onResponseHeadersReceived(int httpStatusCode, String[] headers) { | |
370 mResponseInfo = prepareResponseInfoOnNetworkThread(httpStatusCode, heade rs); | |
371 Runnable task = new Runnable() { | |
372 public void run() { | |
373 synchronized (mNativeStreamLock) { | |
374 if (isDone()) { | |
375 return; | |
376 } | |
377 mStreamState = STATE_WAITING_ON_READ; | |
378 } | |
379 | |
380 try { | |
381 mCallback.onResponseHeadersReceived( | |
382 CronetBidirectionalStream.this, mResponseInfo); | |
383 } catch (Exception e) { | |
384 onCallbackException(e); | |
385 } | |
386 } | |
387 }; | |
388 postTaskToExecutor(task); | |
389 } | |
390 | |
391 @SuppressWarnings("unused") | |
392 @CalledByNative | |
393 private void onReadCompleted(final ByteBuffer byteBuffer, int bytesRead, int initialPosition, | |
394 long receivedBytesCount) { | |
395 mResponseInfo.setReceivedBytesCount(receivedBytesCount); | |
396 if (byteBuffer.position() != initialPosition) { | |
397 failWithException( | |
398 new CronetException("ByteBuffer modified externally during r ead", null)); | |
399 return; | |
400 } | |
401 if (bytesRead < 0 || initialPosition + bytesRead > byteBuffer.limit()) { | |
402 failWithException(new CronetException("Invalid number of bytes read" , null)); | |
403 return; | |
404 } | |
405 if (mOnReadCompletedTask == null) { | |
406 mOnReadCompletedTask = new OnReadCompletedRunnable(); | |
407 } | |
408 byteBuffer.position(initialPosition + bytesRead); | |
409 mOnReadCompletedTask.mByteBuffer = byteBuffer; | |
410 mOnReadCompletedTask.mEndOfStream = (bytesRead == 0); | |
411 postTaskToExecutor(mOnReadCompletedTask); | |
412 } | |
413 | |
414 @SuppressWarnings("unused") | |
415 @CalledByNative | |
416 private void onWriteCompleted(final ByteBuffer byteBuffer, int initialPositi on) { | |
417 if (byteBuffer.position() != initialPosition) { | |
418 failWithException( | |
419 new CronetException("ByteBuffer modified externally during w rite", null)); | |
420 return; | |
421 } | |
422 if (mOnWriteCompletedTask == null) { | |
423 mOnWriteCompletedTask = new OnWriteCompletedRunnable(); | |
424 } | |
425 // Current implementation always writes the complete buffer. | |
426 byteBuffer.position(byteBuffer.limit()); | |
427 mOnWriteCompletedTask.mByteBuffer = byteBuffer; | |
428 postTaskToExecutor(mOnWriteCompletedTask); | |
429 } | |
430 | |
431 @SuppressWarnings("unused") | |
432 @CalledByNative | |
433 private void onResponseTrailersReceived(String[] trailers) { | |
434 final UrlResponseInfo.HeaderBlock trailersBlock = | |
435 new UrlResponseInfo.HeaderBlock(headersListFromStrings(trailers) ); | |
436 Runnable task = new Runnable() { | |
437 public void run() { | |
438 synchronized (mNativeStreamLock) { | |
439 if (isDone()) { | |
440 return; | |
441 } | |
442 } | |
443 try { | |
444 mCallback.onResponseTrailersReceived( | |
445 CronetBidirectionalStream.this, mResponseInfo, trail ersBlock); | |
446 } catch (Exception e) { | |
447 onCallbackException(e); | |
448 } | |
449 } | |
450 }; | |
451 postTaskToExecutor(task); | |
452 } | |
453 | |
454 @SuppressWarnings("unused") | |
455 @CalledByNative | |
456 private void onError(final int nativeError, final String errorString, long r eceivedBytesCount) { | |
457 if (mResponseInfo != null) { | |
458 mResponseInfo.setReceivedBytesCount(receivedBytesCount); | |
459 } | |
460 failWithException(new CronetException( | |
461 "Exception in BidirectionalStream: " + errorString, nativeError) ); | |
462 } | |
463 | |
464 /** | |
465 * Called when request is canceled, no callbacks will be called afterwards. | |
466 */ | |
467 @SuppressWarnings("unused") | |
468 @CalledByNative | |
469 private void onCanceled() { | |
470 Runnable task = new Runnable() { | |
471 public void run() { | |
472 try { | |
473 mCallback.onCanceled(CronetBidirectionalStream.this, mRespon seInfo); | |
474 } catch (Exception e) { | |
475 Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in onCance led method", e); | |
476 } | |
477 } | |
478 }; | |
479 postTaskToExecutor(task); | |
480 } | |
481 | |
482 @VisibleForTesting | |
483 public void setOnDestroyedCallbackForTests(Runnable onDestroyedCallbackForTe sts) { | |
xunjieli
2015/12/18 19:11:11
nit: ForTests vs ForTesting. Should we standardize
mef
2015/12/29 20:36:54
Yes, and ForTesting is more consistent with @Visib
| |
484 mOnDestroyedCallbackForTests = onDestroyedCallbackForTests; | |
485 } | |
486 | |
487 /** | |
488 * Posts task to application Executor. Used for callbacks | |
489 * and other tasks that should not be executed on network thread. | |
490 */ | |
491 private void postTaskToExecutor(Runnable task) { | |
492 try { | |
493 mExecutor.execute(task); | |
494 } catch (RejectedExecutionException failException) { | |
495 Log.e(CronetUrlRequestContext.LOG_TAG, "Exception posting task to ex ecutor", | |
496 failException); | |
497 // If posting a task throws an exception, then there is no choice | |
498 // but to cancel the stream. | |
499 cancel(); | |
500 } | |
501 } | |
502 | |
503 private ArrayList<Map.Entry<String, String>> headersListFromStrings(String[] headers) { | |
xunjieli
2015/12/18 19:11:12
nit: private static.
mef
2015/12/29 20:36:54
Done.
| |
504 ArrayList<Map.Entry<String, String>> headersList = | |
505 new ArrayList<Map.Entry<String, String>>(); | |
506 for (int i = 0; i < headers.length; i += 2) { | |
507 headersList.add(new AbstractMap.SimpleImmutableEntry<String, String> ( | |
508 headers[i], headers[i + 1])); | |
509 } | |
510 return headersList; | |
511 } | |
512 | |
513 private String[] stringsFromHeaderList(ArrayList<Map.Entry<String, String>> headersList) { | |
xunjieli
2015/12/18 19:11:12
nit: private static.
mef
2015/12/29 20:36:54
Done.
| |
514 String headersArray[] = new String[headersList.size() * 2]; | |
515 int i = 0; | |
516 for (Map.Entry<String, String> requestHeader : headersList) { | |
517 headersArray[i++] = requestHeader.getKey(); | |
518 headersArray[i++] = requestHeader.getValue(); | |
519 } | |
520 return headersArray; | |
521 } | |
522 | |
523 private UrlResponseInfo prepareResponseInfoOnNetworkThread( | |
524 int httpStatusCode, String[] headers) { | |
525 long nativeStream; | |
526 synchronized (mNativeStreamLock) { | |
527 if (mNativeStream == 0) { | |
528 return null; | |
529 } | |
530 // This method is running on network thread, so even if | |
531 // mUrlRequestAdapter is set to 0 from another thread the actual | |
532 // deletion of the adapter is posted to network thread, so it is | |
533 // safe to preserve and use urlRequestAdapter outside the lock. | |
xunjieli
2015/12/18 19:11:11
nit: need to update comment. s/mUrlRequestAdapter/
mef
2015/12/29 20:36:54
Done.
| |
534 nativeStream = mNativeStream; | |
535 } | |
536 | |
537 ArrayList<String> urlChain = new ArrayList<String>(); | |
538 urlChain.add(mInitialUrl); | |
539 | |
540 boolean wasCached = false; | |
541 String httpStatusText = ""; | |
542 String negotiatedProtocol = nativeGetNegotiatedProtocol(nativeStream); | |
543 String proxyServer = null; | |
544 | |
545 UrlResponseInfo responseInfo = new UrlResponseInfo(urlChain, httpStatusC ode, httpStatusText, | |
546 headersListFromStrings(headers), wasCached, negotiatedProtocol, proxyServer); | |
547 return responseInfo; | |
548 } | |
549 | |
550 private void destroyNativeStream(boolean sendOnCanceled) { | |
551 synchronized (mNativeStreamLock) { | |
552 Log.i(CronetUrlRequestContext.LOG_TAG, "destroyNativeStream " + this .toString()); | |
553 if (mNativeStream == 0) { | |
554 return; | |
555 } | |
556 nativeDestroy(mNativeStream, sendOnCanceled); | |
557 mRequestContext.onRequestDestroyed(); | |
558 mNativeStream = 0; | |
559 if (mOnDestroyedCallbackForTests != null) { | |
560 mOnDestroyedCallbackForTests.run(); | |
561 } | |
562 } | |
563 } | |
564 | |
565 /** | |
566 * If callback method throws an exception, stream gets canceled | |
567 * and exception is reported via onFailed callback. | |
568 * Only called on the Executor. | |
569 */ | |
570 private void onCallbackException(Exception e) { | |
571 CronetException streamError = | |
572 new CronetException("CalledByNative method has thrown an excepti on", e); | |
573 Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in CalledByNative meth od", e); | |
574 // Do not call into listener if request is complete. | |
575 synchronized (mNativeStreamLock) { | |
576 if (isDone()) { | |
577 return; | |
578 } | |
579 destroyNativeStream(false); | |
580 } | |
581 try { | |
582 mCallback.onFailed(this, mResponseInfo, streamError); | |
583 } catch (Exception failException) { | |
584 Log.e(CronetUrlRequestContext.LOG_TAG, "Exception notifying of faile d request", | |
585 failException); | |
586 } | |
587 } | |
588 | |
589 /** | |
590 * Fails the stream with an exception. Can be called on any thread. | |
591 */ | |
592 private void failWithException(final CronetException exception) { | |
593 Runnable task = new Runnable() { | |
594 public void run() { | |
595 synchronized (mNativeStreamLock) { | |
596 if (isDone()) { | |
597 return; | |
598 } | |
599 mStreamState = STATE_ERROR; | |
600 destroyNativeStream(false); | |
601 } | |
602 try { | |
603 mCallback.onFailed(CronetBidirectionalStream.this, mResponse Info, exception); | |
604 } catch (Exception e) { | |
605 Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in onError method", e); | |
606 } | |
607 } | |
608 }; | |
609 postTaskToExecutor(task); | |
610 } | |
611 | |
612 // Native methods are implemented in cronet_bidirectional_stream.cc. | |
613 private native long nativeCreateBidirectionalStream(long urlRequestContextAd apter); | |
614 | |
615 @NativeClassQualifiedName("CronetBidirectionalStream") | |
616 private native int nativeStart( | |
617 long nativePtr, String url, String method, String[] headers, boolean endOfStream); | |
618 | |
619 @NativeClassQualifiedName("CronetBidirectionalStream") | |
620 private native boolean nativeReadData( | |
621 long nativePtr, ByteBuffer byteBuffer, int position, int capacity); | |
622 | |
623 @NativeClassQualifiedName("CronetBidirectionalStream") | |
624 private native boolean nativeWriteData( | |
625 long nativePtr, ByteBuffer byteBuffer, int position, int capacity, b oolean endOfStream); | |
626 | |
627 @NativeClassQualifiedName("CronetBidirectionalStream") | |
628 private native void nativeDestroy(long nativePtr, boolean sendOnCanceled); | |
629 | |
630 @NativeClassQualifiedName("CronetBidirectionalStream") | |
631 private native String nativeGetNegotiatedProtocol(long nativePtr); | |
632 } | |
OLD | NEW |