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 org.chromium.base.Log; | |
8 import org.chromium.base.VisibleForTesting; | |
9 import org.chromium.base.annotations.CalledByNative; | |
10 import org.chromium.base.annotations.JNINamespace; | |
11 import org.chromium.base.annotations.NativeClassQualifiedName; | |
12 | |
13 import java.nio.ByteBuffer; | |
14 import java.util.AbstractMap; | |
15 import java.util.ArrayList; | |
16 import java.util.List; | |
17 import java.util.Map; | |
18 import java.util.concurrent.Executor; | |
19 import java.util.concurrent.RejectedExecutionException; | |
20 | |
21 import javax.annotation.concurrent.GuardedBy; | |
22 | |
23 /** | |
24 * BidirectionalStream implementation using Chromium network stack. | |
25 * All @CallByNative methods are called on native network thread | |
26 * and post tasks with callback calls onto Executor. Upon return from callback n ative | |
27 * stream is called on executor thread and posts native tasks to native network thread. | |
28 */ | |
29 @JNINamespace("cronet") | |
30 class CronetBidirectionalStream extends BidirectionalStream { | |
31 /** | |
32 * States of BidirectionalStream are tracked in mStreamState and mWriteState . | |
33 * The write state is separated out as it changes independently of the strea m state. | |
34 * There is one initial state - STATE_NOT_STARTED. There is one final state - STATE_SUCCESS, | |
35 * reached after STATE_READING_DONE and STATE_WRITING_DONE. There are 2 exce ption states - | |
36 * STATE_CANCELED and STATE_ERROR, which can be reached from any other state except | |
37 * STATE_SUCCESS. | |
38 */ | |
39 /* Initial state, stream not started. */ | |
40 private static final int STATE_NOT_STARTED = 0; | |
pauljensen
2016/01/06 17:18:33
let's change these states to an enum (they're allo
mef
2016/01/06 21:28:30
Done.
| |
41 /* Stream started, request headers are sent. */ | |
42 private static final int STATE_STARTED = 1; | |
43 /* Waiting for {@code read()} to be called. */ | |
44 private static final int STATE_WAITING_ON_READ = 2; | |
45 /* Reading from the remote, {@code onReadCompleted()} callback will be calle d when done. */ | |
46 private static final int STATE_READING = 3; | |
47 /* There is no more data to read and stream is half-closed by the remote sid e. */ | |
48 private static final int STATE_READING_DONE = 4; | |
49 /* Stream is canceled. */ | |
50 private static final int STATE_CANCELED = 5; | |
pauljensen
2016/01/04 19:56:00
no action required side-note: this state is set bu
mef
2016/01/04 22:27:07
Good point, not sure where to check them. I think
pauljensen
2016/01/06 17:18:33
I don't think we need to take any action, or at le
| |
51 /* Error has occured, stream is closed. */ | |
52 private static final int STATE_ERROR = 6; | |
53 /* Reading and writing is done, and the stream is closed successfully. */ | |
54 private static final int STATE_SUCCESS = 7; | |
55 /* Waiting for {@code write()} to be called. */ | |
56 private static final int STATE_WAITING_ON_WRITE = 10; | |
57 /* Writing to the remote, {@code onWriteCompleted()} callback will be called when done. */ | |
58 private static final int STATE_WRITING = 11; | |
59 /* Writing the last frame, so {@code STATE_WRITING_DONE} will be set upon co mpletion. */ | |
60 private static final int STATE_WRITING_END_OF_STREAM = 12; | |
61 /* There is no more data to write and stream is half-closed by the local sid e. */ | |
62 private static final int STATE_WRITING_DONE = 13; | |
63 | |
64 /* | |
pauljensen
2016/01/06 17:18:33
can we move these four lines down to line 75 so th
mef
2016/01/06 21:28:30
Done.
| |
65 * Synchronize access to mNativeStream, mStreamState and mWriteState. | |
66 */ | |
67 private final Object mNativeStreamLock = new Object(); | |
68 private final CronetUrlRequestContext mRequestContext; | |
69 private final Executor mExecutor; | |
70 private final Callback mCallback; | |
71 private final String mInitialUrl; | |
72 private final int mInitialPriority; | |
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 ; | |
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 | |
pauljensen
2016/01/04 19:56:00
does mWriteState ever proceed to SUCCESS?
mef
2016/01/04 22:27:07
Currently it stops at WRITING_DONE. I could fix th
pauljensen
2016/01/06 17:18:33
As we discussed offline, I think for consistency a
mef
2016/01/06 21:28:30
Done. I wonder whether we need all 3 - Stream Stat
| |
90 */ | |
91 @GuardedBy("mNativeStreamLock") private int mWriteState = STATE_NOT_STARTED; | |
92 | |
93 private UrlResponseInfo mResponseInfo; | |
pauljensen
2016/01/06 17:18:33
should this be marked volatile? it's written on o
mef
2016/01/06 21:28:30
mResponseInfo doesn't change after creation in onR
| |
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 mOnDestroyedCallbackForTesting; | |
108 | |
109 private final class OnReadCompletedRunnable implements Runnable { | |
110 // Buffer passed back from current invocation of onReadCompleted. | |
111 ByteBuffer mByteBuffer; | |
112 // End of stream flag from current invocation of onReadCompleted. | |
113 boolean mEndOfStream; | |
114 | |
115 @Override | |
116 public void run() { | |
117 if (isDone()) { | |
118 return; | |
119 } | |
120 try { | |
121 synchronized (mNativeStreamLock) { | |
122 if (mNativeStream == 0) { | |
123 return; | |
124 } | |
125 if (mEndOfStream) { | |
126 mStreamState = STATE_READING_DONE; | |
127 if (maybeSucceeded()) return; | |
128 } else { | |
129 mStreamState = STATE_WAITING_ON_READ; | |
130 } | |
131 } | |
132 // Null out mByteBuffer, out of paranoia. Has to be done before | |
pauljensen
2016/01/04 19:56:00
nit: "out of paranoia" seems more like to facilita
mef
2016/01/04 22:27:07
Good point. The comment hasn't changed since times
mef
2016/01/06 21:28:30
Done.
| |
133 // mCallback call, to avoid any race when there are multiple | |
134 // executor threads. | |
135 ByteBuffer buffer = mByteBuffer; | |
136 mByteBuffer = null; | |
137 mCallback.onReadCompleted(CronetBidirectionalStream.this, mRespo nseInfo, buffer); | |
138 } catch (Exception e) { | |
139 onCallbackException(e); | |
140 } | |
141 } | |
142 } | |
143 | |
144 private final class OnWriteCompletedRunnable implements Runnable { | |
145 // Buffer passed back from current invocation of onWriteCompleted. | |
146 ByteBuffer mByteBuffer; | |
147 | |
148 @Override | |
149 public void run() { | |
150 if (isDone()) { | |
151 return; | |
152 } | |
153 try { | |
154 synchronized (mNativeStreamLock) { | |
155 if (mNativeStream == 0) { | |
156 return; | |
157 } | |
158 if (mWriteState == STATE_WRITING_END_OF_STREAM) { | |
159 mWriteState = STATE_WRITING_DONE; | |
160 if (maybeSucceeded()) return; | |
161 } else { | |
162 mWriteState = STATE_WAITING_ON_WRITE; | |
163 } | |
164 } | |
165 // Null out mByteBuffer, out of paranoia. Has to be done before | |
166 // mCallback call, to avoid any race when there are multiple | |
167 // executor threads. | |
168 ByteBuffer buffer = mByteBuffer; | |
169 mByteBuffer = null; | |
170 mCallback.onWriteCompleted(CronetBidirectionalStream.this, mResp onseInfo, buffer); | |
171 } catch (Exception e) { | |
172 onCallbackException(e); | |
173 } | |
174 } | |
175 } | |
176 | |
177 CronetBidirectionalStream(CronetUrlRequestContext requestContext, long urlRe questContextAdapter, | |
178 String url, @BidirectionalStream.Builder.StreamPriority int priority , Callback callback, | |
179 Executor executor, String httpMethod, List<Map.Entry<String, String> > requestHeaders) { | |
180 mRequestContext = requestContext; | |
181 mInitialUrl = url; | |
182 mInitialPriority = convertStreamPriority(priority); | |
183 mCallback = callback; | |
184 mExecutor = executor; | |
185 mInitialMethod = httpMethod; | |
186 mRequestHeaders = new ArrayList<Map.Entry<String, String>>(requestHeader s); | |
187 } | |
188 | |
189 @GuardedBy("nativeStreamLock") | |
190 private boolean maybeSucceeded() { | |
191 if (mStreamState != STATE_READING_DONE || mWriteState != STATE_WRITING_D ONE) { | |
192 return false; | |
193 } | |
194 | |
195 mStreamState = STATE_SUCCESS; | |
196 Runnable task = new Runnable() { | |
197 public void run() { | |
198 synchronized (mNativeStreamLock) { | |
199 if (isDone()) { | |
200 return; | |
201 } | |
202 // Destroy native stream first, so request context could be shut | |
203 // down from the listener. | |
204 destroyNativeStream(false); | |
205 } | |
206 try { | |
207 mCallback.onSucceeded(CronetBidirectionalStream.this, mRespo nseInfo); | |
208 } catch (Exception e) { | |
209 Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in onSucce eded method", e); | |
210 } | |
211 } | |
212 }; | |
213 postTaskToExecutor(task); | |
214 return true; | |
215 } | |
216 | |
217 private static boolean doesMethodAllowWriteData(String methodName) { | |
218 return !methodName.equals("GET") && !methodName.equals("HEAD"); | |
219 } | |
220 | |
221 @Override | |
222 public void start() { | |
223 synchronized (mNativeStreamLock) { | |
224 if (mStreamState != STATE_NOT_STARTED) { | |
225 throw new IllegalStateException("Stream is already started."); | |
226 } | |
227 try { | |
228 mNativeStream = nativeCreateBidirectionalStream( | |
229 mRequestContext.getUrlRequestContextAdapter()); | |
230 mRequestContext.onRequestStarted(); | |
231 String headers[] = stringsFromHeaderList(mRequestHeaders); | |
pauljensen
2016/01/04 19:56:00
no action required side-note: interesting that yo
mef
2016/01/04 22:27:07
She didn't change request headers because we didn'
| |
232 // Non-zero startResult means an argument error. | |
233 int startResult = nativeStart(mNativeStream, mInitialUrl, mIniti alPriority, | |
234 mInitialMethod, headers, !doesMethodAllowWriteData(mInit ialMethod)); | |
pauljensen
2016/01/06 17:18:33
Can we combine nativeStart() and nativeCreateBidir
mef
2016/01/06 21:28:30
We could, but then how do we report back faulty me
pauljensen
2016/01/07 02:56:34
Yuck. JNI is awful sometimes. All we need is a p
mef
2016/01/07 03:22:15
Acknowledged.
pauljensen
2016/01/11 20:05:45
Another idea is to throw the exception from native
mef
2016/01/11 23:22:50
Would it make sense to add a @CalledByNative metod
pauljensen
2016/01/12 16:55:41
I don't think there is any need to involve an Exec
mef
2016/01/14 21:07:54
sg, let's keep it this way and refactor both UrlRe
| |
235 if (startResult == -1) { | |
236 throw new IllegalArgumentException("Invalid http method " + mInitialMethod); | |
237 } | |
238 if (startResult > 0) { | |
239 int headerPos = startResult - 1; | |
240 throw new IllegalArgumentException( | |
241 "Invalid header " + headers[headerPos] + "=" + heade rs[headerPos + 1]); | |
242 } | |
243 mStreamState = STATE_STARTED; | |
244 } catch (RuntimeException e) { | |
245 // If there's an exception, cleanup and then throw the | |
246 // exception to the caller. | |
247 destroyNativeStream(false); | |
248 throw e; | |
249 } | |
250 } | |
251 } | |
252 | |
253 @Override | |
254 public void read(ByteBuffer buffer) { | |
255 synchronized (mNativeStreamLock) { | |
256 if (!buffer.hasRemaining()) { | |
257 throw new IllegalArgumentException("ByteBuffer is already full." ); | |
258 } | |
259 if (mStreamState != STATE_WAITING_ON_READ) { | |
260 throw new IllegalStateException("Unexpected read attempt."); | |
261 } | |
262 if (isDone()) { | |
263 return; | |
264 } | |
265 mStreamState = STATE_READING; | |
266 if (!nativeReadData(mNativeStream, buffer, buffer.position(), buffer .limit())) { | |
267 // Still waiting on read. This is just to have consistent | |
268 // behavior with the other error cases. | |
269 mStreamState = STATE_WAITING_ON_READ; | |
270 // Since accessing byteBuffer's memory failed, it's presumably | |
271 // not a direct ByteBuffer. | |
272 throw new IllegalArgumentException("byteBuffer must be a direct ByteBuffer."); | |
273 } | |
274 } | |
275 } | |
276 | |
277 @Override | |
278 public void write(ByteBuffer buffer, boolean endOfStream) { | |
279 synchronized (mNativeStreamLock) { | |
280 if (!buffer.hasRemaining() && !endOfStream) { | |
281 throw new IllegalArgumentException("Empty buffer before end of s tream."); | |
282 } | |
283 if (mWriteState != STATE_WAITING_ON_WRITE) { | |
284 throw new IllegalStateException("Unexpected write attempt."); | |
285 } | |
286 if (isDone()) { | |
287 return; | |
288 } | |
289 mWriteState = endOfStream ? STATE_WRITING_END_OF_STREAM : STATE_WRIT ING; | |
290 if (!nativeWriteData( | |
291 mNativeStream, buffer, buffer.position(), buffer.limit() , endOfStream)) { | |
292 // Still waiting on write. This is just to have consistent | |
293 // behavior with the other error cases. | |
294 mWriteState = STATE_WAITING_ON_WRITE; | |
295 // Since accessing byteBuffer's memory failed, it's presumably | |
296 // not a direct ByteBuffer. | |
297 throw new IllegalArgumentException("byteBuffer must be a direct ByteBuffer."); | |
298 } | |
299 } | |
300 } | |
301 | |
302 @Override | |
303 public void ping(PingCallback callback, Executor executor) { | |
304 // TODO(mef): May be last thing to be implemented on Android. | |
305 throw new UnsupportedOperationException("ping is not supported yet."); | |
306 } | |
307 | |
308 @Override | |
309 public void windowUpdate(int windowSizeIncrement) { | |
310 // TODO(mef): Understand the needs and semantics of this method. | |
311 throw new UnsupportedOperationException("windowUpdate is not supported y et."); | |
312 } | |
313 | |
314 @Override | |
315 public void cancel() { | |
316 synchronized (mNativeStreamLock) { | |
317 if (isDone() || mStreamState == STATE_NOT_STARTED) { | |
318 return; | |
319 } | |
320 mStreamState = STATE_CANCELED; | |
321 destroyNativeStream(true); | |
322 } | |
323 } | |
324 | |
325 @Override | |
326 public boolean isDone() { | |
pauljensen
2016/01/06 17:18:33
This function is only called from within "synchron
mef
2016/01/06 21:28:30
It is also public API, so it has to be synchronize
pauljensen
2016/01/07 02:56:34
how about:
isDone() {
synchronized (mNativeStrea
mef
2016/01/07 03:22:15
I think it is micro-optimization when lock is alre
| |
327 synchronized (mNativeStreamLock) { | |
328 return mStreamState != STATE_NOT_STARTED && mNativeStream == 0; | |
329 } | |
330 } | |
331 | |
332 @SuppressWarnings("unused") | |
333 @CalledByNative | |
334 private void onRequestHeadersSent() { | |
335 Runnable task = new Runnable() { | |
336 public void run() { | |
337 synchronized (mNativeStreamLock) { | |
338 if (isDone()) { | |
339 return; | |
340 } | |
341 if (doesMethodAllowWriteData(mInitialMethod)) { | |
342 mWriteState = STATE_WAITING_ON_WRITE; | |
343 } else { | |
344 mWriteState = STATE_WRITING_DONE; | |
345 } | |
346 } | |
347 | |
348 try { | |
349 mCallback.onRequestHeadersSent(CronetBidirectionalStream.thi s); | |
350 } catch (Exception e) { | |
351 onCallbackException(e); | |
352 } | |
353 } | |
354 }; | |
355 postTaskToExecutor(task); | |
356 } | |
357 | |
358 /** | |
359 * Called when the final set of headers, after all redirects, | |
360 * is received. Can only be called once for each stream. | |
361 */ | |
362 @SuppressWarnings("unused") | |
363 @CalledByNative | |
364 private void onResponseHeadersReceived(int httpStatusCode, String[] headers) { | |
365 mResponseInfo = prepareResponseInfoOnNetworkThread(httpStatusCode, heade rs); | |
366 Runnable task = new Runnable() { | |
367 public void run() { | |
368 synchronized (mNativeStreamLock) { | |
369 if (isDone()) { | |
370 return; | |
371 } | |
372 mStreamState = STATE_WAITING_ON_READ; | |
373 } | |
374 | |
375 try { | |
376 mCallback.onResponseHeadersReceived( | |
377 CronetBidirectionalStream.this, mResponseInfo); | |
378 } catch (Exception e) { | |
379 onCallbackException(e); | |
380 } | |
381 } | |
382 }; | |
383 postTaskToExecutor(task); | |
384 } | |
385 | |
386 @SuppressWarnings("unused") | |
387 @CalledByNative | |
388 private void onReadCompleted(final ByteBuffer byteBuffer, int bytesRead, int initialPosition, | |
389 long receivedBytesCount) { | |
390 mResponseInfo.setReceivedBytesCount(receivedBytesCount); | |
391 if (byteBuffer.position() != initialPosition) { | |
392 failWithException( | |
393 new CronetException("ByteBuffer modified externally during r ead", null)); | |
394 return; | |
395 } | |
396 if (bytesRead < 0 || initialPosition + bytesRead > byteBuffer.limit()) { | |
397 failWithException(new CronetException("Invalid number of bytes read" , null)); | |
398 return; | |
399 } | |
400 if (mOnReadCompletedTask == null) { | |
401 mOnReadCompletedTask = new OnReadCompletedRunnable(); | |
402 } | |
403 byteBuffer.position(initialPosition + bytesRead); | |
404 mOnReadCompletedTask.mByteBuffer = byteBuffer; | |
405 mOnReadCompletedTask.mEndOfStream = (bytesRead == 0); | |
406 postTaskToExecutor(mOnReadCompletedTask); | |
407 } | |
408 | |
409 @SuppressWarnings("unused") | |
410 @CalledByNative | |
411 private void onWriteCompleted(final ByteBuffer byteBuffer, int initialPositi on) { | |
412 if (byteBuffer.position() != initialPosition) { | |
pauljensen
2016/01/04 19:56:00
no action required side-note: Kinda weird to me t
mef
2016/01/04 22:27:07
Good point, I will preserve and check limits as we
mef
2016/01/06 21:28:30
Done.
| |
413 failWithException( | |
414 new CronetException("ByteBuffer modified externally during w rite", null)); | |
415 return; | |
416 } | |
417 if (mOnWriteCompletedTask == null) { | |
418 mOnWriteCompletedTask = new OnWriteCompletedRunnable(); | |
419 } | |
420 // Current implementation always writes the complete buffer. | |
421 byteBuffer.position(byteBuffer.limit()); | |
422 mOnWriteCompletedTask.mByteBuffer = byteBuffer; | |
423 postTaskToExecutor(mOnWriteCompletedTask); | |
424 } | |
425 | |
426 @SuppressWarnings("unused") | |
427 @CalledByNative | |
428 private void onResponseTrailersReceived(String[] trailers) { | |
429 final UrlResponseInfo.HeaderBlock trailersBlock = | |
430 new UrlResponseInfo.HeaderBlock(headersListFromStrings(trailers) ); | |
431 Runnable task = new Runnable() { | |
432 public void run() { | |
433 synchronized (mNativeStreamLock) { | |
434 if (isDone()) { | |
435 return; | |
436 } | |
437 } | |
438 try { | |
439 mCallback.onResponseTrailersReceived( | |
440 CronetBidirectionalStream.this, mResponseInfo, trail ersBlock); | |
441 } catch (Exception e) { | |
442 onCallbackException(e); | |
443 } | |
444 } | |
445 }; | |
446 postTaskToExecutor(task); | |
447 } | |
448 | |
449 @SuppressWarnings("unused") | |
450 @CalledByNative | |
451 private void onError(final int nativeError, final String errorString, long r eceivedBytesCount) { | |
452 if (mResponseInfo != null) { | |
453 mResponseInfo.setReceivedBytesCount(receivedBytesCount); | |
454 } | |
455 failWithException(new CronetException( | |
456 "Exception in BidirectionalStream: " + errorString, nativeError) ); | |
457 } | |
458 | |
459 /** | |
460 * Called when request is canceled, no callbacks will be called afterwards. | |
461 */ | |
462 @SuppressWarnings("unused") | |
463 @CalledByNative | |
464 private void onCanceled() { | |
465 Runnable task = new Runnable() { | |
466 public void run() { | |
467 try { | |
468 mCallback.onCanceled(CronetBidirectionalStream.this, mRespon seInfo); | |
469 } catch (Exception e) { | |
470 Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in onCance led method", e); | |
471 } | |
472 } | |
473 }; | |
474 postTaskToExecutor(task); | |
475 } | |
476 | |
477 @VisibleForTesting | |
478 public void setOnDestroyedCallbackForTesting(Runnable onDestroyedCallbackFor Testing) { | |
479 mOnDestroyedCallbackForTesting = onDestroyedCallbackForTesting; | |
480 } | |
481 | |
482 /** | |
483 * Posts task to application Executor. Used for callbacks | |
484 * and other tasks that should not be executed on network thread. | |
485 */ | |
486 private void postTaskToExecutor(Runnable task) { | |
487 try { | |
488 mExecutor.execute(task); | |
489 } catch (RejectedExecutionException failException) { | |
490 Log.e(CronetUrlRequestContext.LOG_TAG, "Exception posting task to ex ecutor", | |
491 failException); | |
492 // If posting a task throws an exception, then there is no choice | |
493 // but to cancel the stream. | |
494 cancel(); | |
495 } | |
496 } | |
497 | |
498 private static ArrayList<Map.Entry<String, String>> headersListFromStrings(S tring[] headers) { | |
499 ArrayList<Map.Entry<String, String>> headersList = | |
500 new ArrayList<Map.Entry<String, String>>(); | |
pauljensen
2016/01/06 17:18:33
()->(headers.length/2)
mef
2016/01/06 21:28:30
Done.
| |
501 for (int i = 0; i < headers.length; i += 2) { | |
502 headersList.add(new AbstractMap.SimpleImmutableEntry<String, String> ( | |
503 headers[i], headers[i + 1])); | |
504 } | |
505 return headersList; | |
506 } | |
507 | |
508 private static String[] stringsFromHeaderList( | |
509 ArrayList<Map.Entry<String, String>> headersList) { | |
510 String headersArray[] = new String[headersList.size() * 2]; | |
511 int i = 0; | |
512 for (Map.Entry<String, String> requestHeader : headersList) { | |
513 headersArray[i++] = requestHeader.getKey(); | |
514 headersArray[i++] = requestHeader.getValue(); | |
515 } | |
516 return headersArray; | |
517 } | |
518 | |
519 private UrlResponseInfo prepareResponseInfoOnNetworkThread( | |
520 int httpStatusCode, String[] headers) { | |
521 long nativeStream; | |
522 synchronized (mNativeStreamLock) { | |
523 if (mNativeStream == 0) { | |
524 return null; | |
525 } | |
526 // This method is running on network thread, so even if | |
527 // mNativeStream is set to 0 from another thread the actual | |
528 // deletion of the adapter is posted to network thread, so it is | |
529 // safe to preserve and use nativeStream outside the lock. | |
530 nativeStream = mNativeStream; | |
531 } | |
532 | |
533 ArrayList<String> urlChain = new ArrayList<String>(); | |
534 urlChain.add(mInitialUrl); | |
535 | |
536 boolean wasCached = false; | |
537 String httpStatusText = ""; | |
538 String negotiatedProtocol = nativeGetNegotiatedProtocol(nativeStream); | |
539 String proxyServer = null; | |
540 | |
541 UrlResponseInfo responseInfo = new UrlResponseInfo(urlChain, httpStatusC ode, httpStatusText, | |
542 headersListFromStrings(headers), wasCached, negotiatedProtocol, proxyServer); | |
543 return responseInfo; | |
544 } | |
545 | |
546 private static int convertStreamPriority( | |
547 @BidirectionalStream.Builder.StreamPriority int priority) { | |
548 switch (priority) { | |
549 case Builder.STREAM_PRIORITY_IDLE: | |
550 return RequestPriority.IDLE; | |
551 case Builder.STREAM_PRIORITY_LOWEST: | |
552 return RequestPriority.LOWEST; | |
553 case Builder.STREAM_PRIORITY_LOW: | |
554 return RequestPriority.LOW; | |
555 case Builder.STREAM_PRIORITY_MEDIUM: | |
556 return RequestPriority.MEDIUM; | |
557 case Builder.STREAM_PRIORITY_HIGHEST: | |
558 return RequestPriority.HIGHEST; | |
559 default: | |
560 return RequestPriority.MEDIUM; | |
561 } | |
562 } | |
563 | |
564 private void destroyNativeStream(boolean sendOnCanceled) { | |
565 synchronized (mNativeStreamLock) { | |
566 Log.i(CronetUrlRequestContext.LOG_TAG, "destroyNativeStream " + this .toString()); | |
567 if (mNativeStream == 0) { | |
568 return; | |
569 } | |
570 nativeDestroy(mNativeStream, sendOnCanceled); | |
571 mRequestContext.onRequestDestroyed(); | |
572 mNativeStream = 0; | |
pauljensen
2016/01/06 17:18:33
can we swap this line with the line above? I'd ra
mef
2016/01/06 21:28:30
Done.
| |
573 if (mOnDestroyedCallbackForTesting != null) { | |
574 mOnDestroyedCallbackForTesting.run(); | |
575 } | |
576 } | |
577 } | |
578 | |
579 /** | |
580 * If callback method throws an exception, stream gets canceled | |
581 * and exception is reported via onFailed callback. | |
582 * Only called on the Executor. | |
583 */ | |
584 private void onCallbackException(Exception e) { | |
585 CronetException streamError = | |
586 new CronetException("CalledByNative method has thrown an excepti on", e); | |
587 Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in CalledByNative meth od", e); | |
588 // Do not call into listener if request is complete. | |
589 synchronized (mNativeStreamLock) { | |
590 if (isDone()) { | |
591 return; | |
592 } | |
593 destroyNativeStream(false); | |
594 } | |
595 try { | |
596 mCallback.onFailed(this, mResponseInfo, streamError); | |
597 } catch (Exception failException) { | |
598 Log.e(CronetUrlRequestContext.LOG_TAG, "Exception notifying of faile d request", | |
599 failException); | |
600 } | |
601 } | |
602 | |
603 /** | |
604 * Fails the stream with an exception. Can be called on any thread. | |
605 */ | |
606 private void failWithException(final CronetException exception) { | |
607 Runnable task = new Runnable() { | |
608 public void run() { | |
609 synchronized (mNativeStreamLock) { | |
610 if (isDone()) { | |
611 return; | |
612 } | |
613 mStreamState = STATE_ERROR; | |
614 destroyNativeStream(false); | |
615 } | |
616 try { | |
617 mCallback.onFailed(CronetBidirectionalStream.this, mResponse Info, exception); | |
618 } catch (Exception e) { | |
619 Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in onError method", e); | |
620 } | |
621 } | |
622 }; | |
623 postTaskToExecutor(task); | |
624 } | |
625 | |
626 // Native methods are implemented in cronet_bidirectional_stream.cc. | |
627 private native long nativeCreateBidirectionalStream(long urlRequestContextAd apter); | |
628 | |
629 @NativeClassQualifiedName("CronetBidirectionalStream") | |
630 private native int nativeStart(long nativePtr, String url, int priority, Str ing method, | |
631 String[] headers, boolean endOfStream); | |
632 | |
633 @NativeClassQualifiedName("CronetBidirectionalStream") | |
634 private native boolean nativeReadData( | |
635 long nativePtr, ByteBuffer byteBuffer, int position, int capacity); | |
636 | |
637 @NativeClassQualifiedName("CronetBidirectionalStream") | |
638 private native boolean nativeWriteData( | |
639 long nativePtr, ByteBuffer byteBuffer, int position, int capacity, b oolean endOfStream); | |
640 | |
641 @NativeClassQualifiedName("CronetBidirectionalStream") | |
642 private native void nativeDestroy(long nativePtr, boolean sendOnCanceled); | |
643 | |
644 @NativeClassQualifiedName("CronetBidirectionalStream") | |
645 private native String nativeGetNegotiatedProtocol(long nativePtr); | |
646 } | |
OLD | NEW |