Chromium Code Reviews| 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.urlconnection; | |
| 6 | |
| 7 import org.chromium.base.VisibleForTesting; | |
| 8 import org.chromium.net.UploadDataProvider; | |
| 9 import org.chromium.net.UploadDataSink; | |
| 10 | |
| 11 import java.io.IOException; | |
| 12 import java.io.OutputStream; | |
| 13 import java.net.ProtocolException; | |
| 14 import java.nio.ByteBuffer; | |
| 15 | |
| 16 /** | |
| 17 * An implementation of {@link java.io.OutputStream} to send data to a server, | |
| 18 * when {@link CronetHttpURLConnection#setFixedLengthStreamingMode} is used. | |
| 19 * This implementation does not buffer the entire request body in memory. | |
| 20 * Note that {@link #write} should only be called from the thread on which the | |
| 21 * {@link #mConnection} is created. | |
|
mmenke
2015/03/25 18:34:57
This should mention "fixedStreamingMode" or "fixed
xunjieli
2015/03/27 17:46:45
Done.
| |
| 22 */ | |
| 23 final class CronetOutputStream extends OutputStream implements UploadDataProvide r { | |
|
mmenke
2015/03/25 18:34:57
Maybe rename to something like CronetFixedModeOutp
xunjieli
2015/03/27 17:46:45
Done.
| |
| 24 // CronetOutputStream buffers up to this value and wait for UploadDataStream | |
| 25 // to consume the data. This field is non-final, so it can be changed for te sts. | |
| 26 // TODO(xunjieli): figure out whether this default value should be changed. | |
| 27 @VisibleForTesting | |
| 28 private static int sDefaultBufferLength = 2048; | |
| 29 private final CronetHttpURLConnection mConnection; | |
| 30 private final long mContentLength; | |
| 31 private final ByteBuffer mBuffer; | |
| 32 private long mBytesReceived; | |
| 33 | |
| 34 /** | |
| 35 * Package protected constructor. | |
| 36 * @param connection The CronetHttpURLConnection object. | |
| 37 * @param contentLength The content length of the request body. Non-zero for | |
| 38 * non-chunked upload. | |
| 39 */ | |
| 40 CronetOutputStream(CronetHttpURLConnection connection, long contentLength) { | |
| 41 if (connection == null) { | |
| 42 throw new NullPointerException(); | |
| 43 } | |
| 44 if (contentLength < 0) { | |
| 45 throw new IllegalArgumentException( | |
| 46 "Content length must be larger than 0 for non-chunked upload ."); | |
| 47 } | |
| 48 mContentLength = contentLength; | |
| 49 int bufferSize = (int) Math.min(mContentLength, sDefaultBufferLength); | |
| 50 mBuffer = ByteBuffer.allocate(bufferSize); | |
| 51 mConnection = connection; | |
| 52 mBytesReceived = 0; | |
| 53 } | |
| 54 | |
| 55 @Override | |
| 56 public void write(int oneByte) throws IOException { | |
| 57 checkNotExceedContentLength(1); | |
| 58 while (mBuffer.position() == mBuffer.limit()) { | |
| 59 // Wait until buffer is consumed. | |
| 60 mConnection.waitForRead(); | |
| 61 } | |
| 62 mBuffer.put((byte) oneByte); | |
| 63 mBytesReceived++; | |
| 64 if (mBytesReceived == mContentLength) { | |
| 65 // Entire post data has been received. Now wait for network stack to | |
| 66 // read it. | |
| 67 mConnection.waitForRead(); | |
| 68 } | |
| 69 } | |
| 70 | |
| 71 @Override | |
| 72 public void write(byte[] buffer, int offset, int count) throws IOException { | |
| 73 if (buffer.length - offset < count || offset < 0 || count < 0) { | |
| 74 throw new IndexOutOfBoundsException(); | |
| 75 } | |
| 76 checkNotExceedContentLength(count); | |
| 77 if (count == 0) { | |
| 78 return; | |
| 79 } | |
| 80 int toSend = count; | |
| 81 while (toSend > 0) { | |
| 82 if (mBuffer.position() == mBuffer.limit()) { | |
| 83 // Wait until buffer is consumed. | |
| 84 mConnection.waitForRead(); | |
| 85 } | |
| 86 int sent = Math.min(toSend, mBuffer.limit() - mBuffer.position()); | |
| 87 mBuffer.put(buffer, offset + count - toSend, sent); | |
| 88 toSend -= sent; | |
| 89 } | |
| 90 mBytesReceived += count; | |
| 91 if (mBytesReceived == mContentLength) { | |
| 92 // Entire post data has been received. Now wait for network stack to | |
| 93 // read it. | |
| 94 mConnection.waitForRead(); | |
| 95 } | |
| 96 } | |
| 97 | |
| 98 // TODO(xunjieli): implement close(). | |
| 99 | |
| 100 /** | |
| 101 * Throws {@link java.net.ProtocolException} if adding {@code numBytes} will | |
| 102 * exceed content length. | |
| 103 */ | |
| 104 private void checkNotExceedContentLength(int numBytes) throws ProtocolExcept ion { | |
| 105 if (mContentLength != -1 && mBytesReceived + numBytes > mContentLength) { | |
| 106 throw new ProtocolException("expected " | |
| 107 + (mContentLength - mBytesReceived) + " bytes but received " | |
| 108 + numBytes); | |
| 109 } | |
| 110 } | |
| 111 | |
| 112 // Below are UploadDataProvider implementations. Only intended to be used | |
| 113 // within Cronet. | |
| 114 | |
| 115 @Override | |
| 116 public long getLength() { | |
| 117 return mContentLength; | |
| 118 } | |
| 119 | |
| 120 @Override | |
| 121 public void read(final UploadDataSink uploadDataSink, final ByteBuffer byteB uffer) { | |
| 122 Runnable readTask = new Runnable() { | |
|
mmenke
2015/03/25 18:34:57
Can't we just inline this instead of making it a t
xunjieli
2015/03/27 17:46:45
Done. I ran into problem with loop() being invoked
| |
| 123 @Override | |
| 124 public void run() { | |
| 125 if (byteBuffer.remaining() < mBuffer.position()) { | |
| 126 // byteBuffer does not have enough capacity, so only put a p ortion | |
| 127 // of mBuffer in it. | |
| 128 mBuffer.position(byteBuffer.remaining()); | |
| 129 byteBuffer.put(mBuffer.array(), 0, byteBuffer.remaining()); | |
| 130 // Move remaining buffer to the head of the buffer for use i n the | |
| 131 // next read call. | |
| 132 mBuffer.compact(); | |
| 133 } else { | |
| 134 // byteBuffer has enough capacity to hold content in mBuffer . | |
| 135 mBuffer.flip(); | |
| 136 byteBuffer.put(mBuffer); | |
| 137 // Reuse this buffer. | |
| 138 mBuffer.clear(); | |
| 139 } | |
| 140 uploadDataSink.onReadSucceeded(false); | |
| 141 } | |
| 142 }; | |
| 143 if (mBuffer.position() == 0) { | |
| 144 // Exit the message loop, so consumer can write more data. | |
| 145 if (mBytesReceived < mContentLength) { | |
| 146 mConnection.postponeRead(readTask); | |
| 147 } | |
| 148 } else { | |
| 149 readTask.run(); | |
| 150 } | |
| 151 } | |
| 152 | |
| 153 @Override | |
| 154 public void rewind(UploadDataSink uploadDataSink) { | |
| 155 uploadDataSink.onRewindError(null); | |
|
mmenke
2015/03/25 18:34:57
Passing a null exception along seems like a bad id
xunjieli
2015/03/27 17:46:45
Done.
| |
| 156 } | |
| 157 | |
| 158 /** | |
| 159 * Sets the default buffer length for use in tests. | |
| 160 */ | |
| 161 @VisibleForTesting | |
| 162 static void setDefaultBufferLengthForTesting(int length) { | |
| 163 sDefaultBufferLength = length; | |
| 164 } | |
| 165 } | |
| OLD | NEW |