OLD | NEW |
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 |
7 import org.apache.http.conn.ConnectTimeoutException; | 9 import org.apache.http.conn.ConnectTimeoutException; |
8 import org.chromium.base.CalledByNative; | 10 import org.chromium.base.CalledByNative; |
9 import org.chromium.base.JNINamespace; | 11 import org.chromium.base.JNINamespace; |
10 | 12 |
11 import java.io.IOException; | 13 import java.io.IOException; |
12 import java.net.MalformedURLException; | 14 import java.net.MalformedURLException; |
13 import java.net.URL; | 15 import java.net.URL; |
14 import java.net.UnknownHostException; | 16 import java.net.UnknownHostException; |
15 import java.nio.ByteBuffer; | 17 import java.nio.ByteBuffer; |
16 import java.nio.channels.ReadableByteChannel; | 18 import java.nio.channels.ReadableByteChannel; |
17 import java.nio.channels.WritableByteChannel; | 19 import java.nio.channels.WritableByteChannel; |
| 20 import java.util.ArrayList; |
18 import java.util.HashMap; | 21 import java.util.HashMap; |
| 22 import java.util.List; |
19 import java.util.Map; | 23 import java.util.Map; |
20 import java.util.Map.Entry; | 24 import java.util.Map.Entry; |
21 import java.util.concurrent.Semaphore; | 25 import java.util.concurrent.Semaphore; |
22 | 26 |
23 /** | 27 /** |
24 * Network request using the native http stack implementation. | 28 * Network request using the native http stack implementation. |
25 */ | 29 */ |
26 @JNINamespace("cronet") | 30 @JNINamespace("cronet") |
27 public class UrlRequest { | 31 public class UrlRequest { |
| 32 private static final String TAG = "UrlRequest"; |
| 33 private static final boolean DBG = false; |
| 34 |
28 private static final class ContextLock { | 35 private static final class ContextLock { |
29 } | 36 } |
30 | 37 |
31 private static final int UPLOAD_BYTE_BUFFER_SIZE = 32768; | 38 private static final int UPLOAD_BYTE_BUFFER_SIZE = 32768; |
32 | 39 |
33 private final UrlRequestContext mRequestContext; | 40 private final UrlRequestContext mRequestContext; |
34 private final String mUrl; | 41 private final String mUrl; |
35 private final int mPriority; | 42 private final int mPriority; |
36 private final Map<String, String> mHeaders; | 43 private final Map<String, String> mHeaders; |
37 private final WritableByteChannel mSink; | 44 private final WritableByteChannel mSink; |
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
116 /** | 123 /** |
117 * Sets a readable byte channel to upload as part of a POST request. | 124 * Sets a readable byte channel to upload as part of a POST request. |
118 * | 125 * |
119 * @param contentType MIME type of the post content or null if this is not a | 126 * @param contentType MIME type of the post content or null if this is not a |
120 * POST request. | 127 * POST request. |
121 * @param channel The channel to read to read upload data from if this is a | 128 * @param channel The channel to read to read upload data from if this is a |
122 * POST request. | 129 * POST request. |
123 */ | 130 */ |
124 public void setUploadChannel(String contentType, | 131 public void setUploadChannel(String contentType, |
125 ReadableByteChannel channel) { | 132 ReadableByteChannel channel) { |
| 133 throw new UnsupportedOperationException("Not implemented"); |
| 134 } |
| 135 |
| 136 public WritableByteChannel enableUpload(String contentType) { |
126 synchronized (mLock) { | 137 synchronized (mLock) { |
127 validateNotStarted(); | 138 validateNotStarted(); |
128 validatePostBodyNotSet(); | 139 validatePostBodyNotSet(); |
129 nativeBeginChunkedUpload(mUrlRequestPeer, contentType); | 140 nativeBeginChunkedUpload(mUrlRequestPeer, contentType); |
130 mPostBodyChannel = channel; | 141 //mPostBodyChannel = new UploadChannel(); |
131 mPostBodySet = true; | 142 mPostBodySet = true; |
132 } | 143 } |
133 mAppendChunkSemaphore = new Semaphore(0); | 144 mAppendChunkSemaphore = new Semaphore(0); |
| 145 return new UploadChannel(); |
134 } | 146 } |
135 | 147 |
136 public WritableByteChannel getSink() { | 148 public WritableByteChannel getSink() { |
137 return mSink; | 149 return mSink; |
138 } | 150 } |
139 | 151 |
140 public void start() { | 152 public void start() { |
141 try { | 153 try { |
142 synchronized (mLock) { | 154 synchronized (mLock) { |
143 if (mCanceled) { | 155 if (mCanceled) { |
(...skipping 16 matching lines...) Expand all Loading... |
160 for (Entry<String, String> entry : | 172 for (Entry<String, String> entry : |
161 mAdditionalHeaders.entrySet()) { | 173 mAdditionalHeaders.entrySet()) { |
162 nativeAddHeader(mUrlRequestPeer, entry.getKey(), | 174 nativeAddHeader(mUrlRequestPeer, entry.getKey(), |
163 entry.getValue()); | 175 entry.getValue()); |
164 } | 176 } |
165 } | 177 } |
166 | 178 |
167 nativeStart(mUrlRequestPeer); | 179 nativeStart(mUrlRequestPeer); |
168 } | 180 } |
169 | 181 |
170 if (mPostBodyChannel != null) { | |
171 uploadFromChannel(mPostBodyChannel); | |
172 } | |
173 } finally { | 182 } finally { |
174 if (mPostBodyChannel != null) { | 183 if (mPostBodyChannel != null) { |
175 try { | 184 try { |
176 mPostBodyChannel.close(); | 185 mPostBodyChannel.close(); |
177 } catch (IOException e) { | 186 } catch (IOException e) { |
178 // Ignore | 187 // Ignore |
179 } | 188 } |
180 } | 189 } |
181 } | 190 } |
182 } | 191 } |
183 | 192 |
184 /** | |
185 * Uploads data from a {@code ReadableByteChannel} using chunked transfer | |
186 * encoding. The native call to append a chunk is asynchronous so a | |
187 * semaphore is used to delay writing into the buffer again until chromium | |
188 * is finished with it. | |
189 * | |
190 * @param channel the channel to read data from. | |
191 */ | |
192 private void uploadFromChannel(ReadableByteChannel channel) { | |
193 ByteBuffer buffer = ByteBuffer.allocateDirect(UPLOAD_BYTE_BUFFER_SIZE); | |
194 | |
195 // The chromium API requires us to specify in advance if a chunk is the | |
196 // last one. This extra ByteBuffer is needed to peek ahead and check for | |
197 // the end of the channel. | |
198 ByteBuffer checkForEnd = ByteBuffer.allocate(1); | |
199 | |
200 try { | |
201 boolean lastChunk; | |
202 do { | |
203 // First dump in the one byte we read to check for the end of | |
204 // the channel. (The first time through the loop the checkForEnd | |
205 // buffer will be empty). | |
206 checkForEnd.flip(); | |
207 buffer.clear(); | |
208 buffer.put(checkForEnd); | |
209 checkForEnd.clear(); | |
210 | |
211 channel.read(buffer); | |
212 lastChunk = channel.read(checkForEnd) <= 0; | |
213 buffer.flip(); | |
214 nativeAppendChunk(mUrlRequestPeer, buffer, buffer.limit(), | |
215 lastChunk); | |
216 | |
217 if (lastChunk) { | |
218 break; | |
219 } | |
220 | |
221 // Acquire permit before writing to the buffer again to ensure | |
222 // chromium is done with it. | |
223 mAppendChunkSemaphore.acquire(); | |
224 } while (!lastChunk && !mFinished); | |
225 } catch (IOException e) { | |
226 mSinkException = e; | |
227 cancel(); | |
228 } catch (InterruptedException e) { | |
229 mSinkException = new IOException(e); | |
230 cancel(); | |
231 } | |
232 } | |
233 | |
234 public void cancel() { | 193 public void cancel() { |
235 synchronized (mLock) { | 194 synchronized (mLock) { |
236 if (mCanceled) { | 195 if (mCanceled) { |
237 return; | 196 return; |
238 } | 197 } |
239 | 198 |
240 mCanceled = true; | 199 mCanceled = true; |
241 | 200 |
242 if (!mRecycled) { | 201 if (!mRecycled) { |
243 nativeCancel(mUrlRequestPeer); | 202 nativeCancel(mUrlRequestPeer); |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
306 | 265 |
307 public String getContentType() { | 266 public String getContentType() { |
308 return mContentType; | 267 return mContentType; |
309 } | 268 } |
310 | 269 |
311 public String getHeader(String name) { | 270 public String getHeader(String name) { |
312 validateHeadersAvailable(); | 271 validateHeadersAvailable(); |
313 return nativeGetHeader(mUrlRequestPeer, name); | 272 return nativeGetHeader(mUrlRequestPeer, name); |
314 } | 273 } |
315 | 274 |
| 275 // All response headers. |
| 276 public Map<String, List<String>> getAllHeaders() { |
| 277 validateHeadersAvailable(); |
| 278 String[] headers = nativeGetAllHeaders(mUrlRequestPeer); |
| 279 Map<String, List<String>> result = new HashMap<String, List<String>>(); |
| 280 for (int i = 0; i < headers.length / 2; ++i) { |
| 281 String key = headers[2 * i]; |
| 282 String value = headers[2 * i + 1]; |
| 283 if (!result.containsKey(key)) { |
| 284 result.put(key, new ArrayList<String>()); |
| 285 } |
| 286 result.get(key).add(value); |
| 287 } |
| 288 return result; |
| 289 } |
| 290 |
| 291 |
316 /** | 292 /** |
317 * A callback invoked when appending a chunk to the request has completed. | 293 * A callback invoked when appending a chunk to the request has completed. |
318 */ | 294 */ |
319 @CalledByNative | 295 @CalledByNative |
320 protected void onAppendChunkCompleted() { | 296 protected void onAppendChunkCompleted() { |
321 mAppendChunkSemaphore.release(); | 297 mAppendChunkSemaphore.release(); |
322 } | 298 } |
323 | 299 |
324 /** | 300 /** |
325 * A callback invoked when the first chunk of the response has arrived. | 301 * A callback invoked when the first chunk of the response has arrived. |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
401 } | 377 } |
402 } | 378 } |
403 | 379 |
404 | 380 |
405 private void validateHeadersAvailable() { | 381 private void validateHeadersAvailable() { |
406 if (!mHeadersAvailable) { | 382 if (!mHeadersAvailable) { |
407 throw new IllegalStateException("Response headers not available"); | 383 throw new IllegalStateException("Response headers not available"); |
408 } | 384 } |
409 } | 385 } |
410 | 386 |
| 387 /** |
| 388 * Invokes {@link #nativeAppendChunk(long, ByteBuffer, int, boolean)} and |
| 389 * waits for it to notify completion. |
| 390 * @param chunk The data. It's position must be zero. |
| 391 * @param isLastChunk Whether chunk is the last one. |
| 392 */ |
| 393 void appendChunkBlocking(ByteBuffer chunk, boolean isLastChunk) |
| 394 throws IOException { |
| 395 if (chunk.position() != 0) { |
| 396 throw new IllegalArgumentException("The position must be zero."); |
| 397 } |
| 398 synchronized (mLock) { |
| 399 if (mUrlRequestPeer == 0) { |
| 400 throw new IOException("Native peer destroyed."); |
| 401 } |
| 402 nativeAppendChunk(mUrlRequestPeer, chunk, chunk.limit(), false); |
| 403 // Wait for the data to be actually consumed. |
| 404 try { |
| 405 mAppendChunkSemaphore.acquire(); |
| 406 } catch (InterruptedException e) { |
| 407 // We were interrupted before the data was uploaded. Recovering |
| 408 // from this state is complicated so we cancel the upload |
| 409 // operation and fail. |
| 410 Thread.currentThread().interrupt(); |
| 411 |
| 412 // TODO(miloslav): Not sure why do we set mSinkException here. |
| 413 mSinkException = new IOException("Upload interrupted", e); |
| 414 cancel(); |
| 415 throw mSinkException; |
| 416 } |
| 417 } |
| 418 } |
| 419 |
411 public String getUrl() { | 420 public String getUrl() { |
412 return mUrl; | 421 return mUrl; |
413 } | 422 } |
414 | 423 |
415 private native long nativeCreateRequestPeer(long urlRequestContextPeer, | 424 private native long nativeCreateRequestPeer(long urlRequestContextPeer, |
416 String url, int priority); | 425 String url, int priority); |
417 | 426 |
418 private native void nativeAddHeader(long urlRequestPeer, String name, | 427 private native void nativeAddHeader(long urlRequestPeer, String name, |
419 String value); | 428 String value); |
420 | 429 |
(...skipping 16 matching lines...) Expand all Loading... |
437 | 446 |
438 private native int nativeGetHttpStatusCode(long urlRequestPeer); | 447 private native int nativeGetHttpStatusCode(long urlRequestPeer); |
439 | 448 |
440 private native String nativeGetErrorString(long urlRequestPeer); | 449 private native String nativeGetErrorString(long urlRequestPeer); |
441 | 450 |
442 private native String nativeGetContentType(long urlRequestPeer); | 451 private native String nativeGetContentType(long urlRequestPeer); |
443 | 452 |
444 private native long nativeGetContentLength(long urlRequestPeer); | 453 private native long nativeGetContentLength(long urlRequestPeer); |
445 | 454 |
446 private native String nativeGetHeader(long urlRequestPeer, String name); | 455 private native String nativeGetHeader(long urlRequestPeer, String name); |
| 456 |
| 457 private native String[] nativeGetAllHeaders(long urlRequestPeer); |
| 458 |
| 459 |
| 460 class UploadChannel implements WritableByteChannel { |
| 461 |
| 462 private boolean mOpen = true; |
| 463 // Native wants a direct buffer. |
| 464 private final ByteBuffer mBuffer = |
| 465 ByteBuffer.allocateDirect(UPLOAD_BYTE_BUFFER_SIZE); |
| 466 |
| 467 @Override |
| 468 public synchronized boolean isOpen() { |
| 469 return mOpen; |
| 470 } |
| 471 |
| 472 @Override |
| 473 public synchronized void close() throws IOException { |
| 474 if (DBG) Log.d(TAG, "UploadChannel.close() url=" + getUrl()); |
| 475 if (!mOpen) { |
| 476 return; |
| 477 } |
| 478 |
| 479 mOpen = false; |
| 480 mBuffer.clear(); |
| 481 |
| 482 // NOOP If the native peer has been destroyed. |
| 483 try { |
| 484 if (DBG) Log.d(TAG, "UploadChannel.close(): final chunk."); |
| 485 appendChunkBlocking(mBuffer, true); |
| 486 } catch (IOException e) { |
| 487 Log.w(TAG, "Ignoring exception during closing.", e); |
| 488 } |
| 489 |
| 490 if (DBG) Log.d(TAG, "UploadChannel.close() done."); |
| 491 } |
| 492 |
| 493 @Override |
| 494 public synchronized int write(ByteBuffer sourceBuffer) |
| 495 throws IOException { |
| 496 if (DBG) Log.d(TAG, "UploadChannel.write(" |
| 497 + sourceBuffer.remaining() + " bytes) url=" + getUrl()); |
| 498 int written = 0; |
| 499 while (sourceBuffer.hasRemaining()) { |
| 500 mBuffer.clear(); |
| 501 int oldLimit = sourceBuffer.limit(); |
| 502 if (sourceBuffer.remaining() > mBuffer.remaining()) { |
| 503 sourceBuffer.limit(sourceBuffer.position() |
| 504 + mBuffer.remaining()); |
| 505 } |
| 506 mBuffer.put(sourceBuffer); |
| 507 mBuffer.flip(); |
| 508 written += mBuffer.limit(); |
| 509 appendChunkBlocking(mBuffer, false); |
| 510 sourceBuffer.limit(oldLimit); |
| 511 } |
| 512 if (DBG) Log.d(TAG, "UploadChannel.write() returning"); |
| 513 return written; |
| 514 } |
| 515 |
| 516 } |
447 } | 517 } |
OLD | NEW |