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.UploadDataSink; |
| 9 |
| 10 import java.io.IOException; |
| 11 import java.net.HttpRetryException; |
| 12 import java.net.ProtocolException; |
| 13 import java.nio.ByteBuffer; |
| 14 |
| 15 /** |
| 16 * An implementation of {@link java.io.OutputStream} to send data to a server, |
| 17 * when {@link CronetHttpURLConnection#setFixedLengthStreamingMode} is used. |
| 18 * This implementation does not buffer the entire request body in memory. |
| 19 * It does not support rewind. Note that {@link #write} should only be called |
| 20 * from the thread on which the {@link #mConnection} is created. |
| 21 */ |
| 22 final class CronetFixedModeOutputStream extends CronetOutputStream { |
| 23 // CronetFixedModeOutputStream buffers up to this value and wait for UploadD
ataStream |
| 24 // to consume the data. This field is non-final, so it can be changed for te
sts. |
| 25 // Using 2048 bytes is because the internal read buffer is 14520 for QUIC, |
| 26 // 2852 for SPDY, and 16384 for normal stream. If a large value is used |
| 27 // here, the buffer might not fit the internal buffer and compacting the buf
fer |
| 28 // will be costly, see #read method below. |
| 29 @VisibleForTesting |
| 30 private static int sDefaultBufferLength = 2048; |
| 31 private final CronetHttpURLConnection mConnection; |
| 32 private final MessageLoop mMessageLoop; |
| 33 private final long mContentLength; |
| 34 private final ByteBuffer mBuffer; |
| 35 private long mBytesWritten; |
| 36 |
| 37 /** |
| 38 * Package protected constructor. |
| 39 * @param connection The CronetHttpURLConnection object. |
| 40 * @param contentLength The content length of the request body. Non-zero for |
| 41 * non-chunked upload. |
| 42 */ |
| 43 CronetFixedModeOutputStream(CronetHttpURLConnection connection, |
| 44 long contentLength, MessageLoop messageLoop) { |
| 45 if (connection == null) { |
| 46 throw new NullPointerException(); |
| 47 } |
| 48 if (contentLength < 0) { |
| 49 throw new IllegalArgumentException( |
| 50 "Content length must be larger than 0 for non-chunked upload
."); |
| 51 } |
| 52 mContentLength = contentLength; |
| 53 int bufferSize = (int) Math.min(mContentLength, sDefaultBufferLength); |
| 54 mBuffer = ByteBuffer.allocate(bufferSize); |
| 55 mConnection = connection; |
| 56 mMessageLoop = messageLoop; |
| 57 mBytesWritten = 0; |
| 58 } |
| 59 |
| 60 @Override |
| 61 public void write(int oneByte) throws IOException { |
| 62 checkNotExceedContentLength(1); |
| 63 while (mBuffer.position() == mBuffer.limit()) { |
| 64 // Wait until buffer is consumed. |
| 65 mMessageLoop.loop(); |
| 66 } |
| 67 mBuffer.put((byte) oneByte); |
| 68 mBytesWritten++; |
| 69 if (mBytesWritten == mContentLength) { |
| 70 // Entire post data has been received. Now wait for network stack to |
| 71 // read it. |
| 72 mMessageLoop.loop(); |
| 73 } |
| 74 } |
| 75 |
| 76 @Override |
| 77 public void write(byte[] buffer, int offset, int count) throws IOException { |
| 78 if (buffer.length - offset < count || offset < 0 || count < 0) { |
| 79 throw new IndexOutOfBoundsException(); |
| 80 } |
| 81 checkNotExceedContentLength(count); |
| 82 if (count == 0) { |
| 83 return; |
| 84 } |
| 85 int toSend = count; |
| 86 while (toSend > 0) { |
| 87 if (mBuffer.position() == mBuffer.limit()) { |
| 88 // Wait until buffer is consumed. |
| 89 mMessageLoop.loop(); |
| 90 } |
| 91 int sent = Math.min(toSend, mBuffer.limit() - mBuffer.position()); |
| 92 mBuffer.put(buffer, offset + count - toSend, sent); |
| 93 toSend -= sent; |
| 94 } |
| 95 mBytesWritten += count; |
| 96 if (mBytesWritten == mContentLength) { |
| 97 // Entire post data has been received. Now wait for network stack to |
| 98 // read it. |
| 99 mMessageLoop.loop(); |
| 100 } |
| 101 } |
| 102 |
| 103 /** |
| 104 * Throws {@link java.net.ProtocolException} if adding {@code numBytes} will |
| 105 * exceed content length. |
| 106 */ |
| 107 private void checkNotExceedContentLength(int numBytes) throws ProtocolExcept
ion { |
| 108 if (mBytesWritten + numBytes > mContentLength) { |
| 109 throw new ProtocolException("expected " |
| 110 + (mContentLength - mBytesWritten) + " bytes but received " |
| 111 + numBytes); |
| 112 } |
| 113 } |
| 114 |
| 115 // TODO(xunjieli): implement close(). |
| 116 |
| 117 // Below are CronetOutputStream implementations: |
| 118 |
| 119 @Override |
| 120 void setConnected() throws IOException { |
| 121 // Do nothing. |
| 122 } |
| 123 |
| 124 @Override |
| 125 void checkReceivedEnoughContent() throws IOException { |
| 126 if (mBytesWritten < mContentLength) { |
| 127 throw new ProtocolException("Content received is less than Content-L
ength."); |
| 128 } |
| 129 } |
| 130 |
| 131 @Override |
| 132 public long getLength() { |
| 133 return mContentLength; |
| 134 } |
| 135 |
| 136 @Override |
| 137 public void read(final UploadDataSink uploadDataSink, final ByteBuffer byteB
uffer) { |
| 138 int availableSpace = byteBuffer.capacity() - byteBuffer.position(); |
| 139 if (availableSpace < mBuffer.position()) { |
| 140 // byteBuffer does not have enough capacity, so only put a portion |
| 141 // of mBuffer in it. |
| 142 byteBuffer.put(mBuffer.array(), 0, availableSpace); |
| 143 mBuffer.position(availableSpace); |
| 144 // Move remaining buffer to the head of the buffer for use in the |
| 145 // next read call. |
| 146 mBuffer.compact(); |
| 147 } else { |
| 148 // byteBuffer has enough capacity to hold the content of mBuffer. |
| 149 mBuffer.flip(); |
| 150 byteBuffer.put(mBuffer); |
| 151 // Reuse this buffer. |
| 152 mBuffer.clear(); |
| 153 // Quit message loop so embedder can write more data. |
| 154 mMessageLoop.postQuitTask(); |
| 155 } |
| 156 uploadDataSink.onReadSucceeded(false); |
| 157 } |
| 158 |
| 159 @Override |
| 160 public void rewind(UploadDataSink uploadDataSink) { |
| 161 uploadDataSink.onRewindError(new HttpRetryException( |
| 162 "Cannot retry streamed Http body", -1)); |
| 163 } |
| 164 |
| 165 /** |
| 166 * Sets the default buffer length for use in tests. |
| 167 */ |
| 168 @VisibleForTesting |
| 169 static void setDefaultBufferLengthForTesting(int length) { |
| 170 sDefaultBufferLength = length; |
| 171 } |
| 172 } |
OLD | NEW |