| 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 |