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

Side by Side Diff: components/cronet/android/java/src/org/chromium/net/UrlRequest.java

Issue 367763004: WIP: Some cronet modifications for the AndroidGSA (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Second attempt on UploadChannel Created 6 years, 5 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
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
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
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
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698