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.net.UploadDataProvider; | |
| 8 import org.chromium.net.UploadDataSink; | |
| 9 | |
| 10 import java.io.IOException; | |
| 11 import java.io.OutputStream; | |
| 12 import java.net.ProtocolException; | |
| 13 import java.nio.ByteBuffer; | |
| 14 | |
| 15 /** | |
| 16 * An implementation of {@link java.io.OutputStream} that buffers entire request | |
| 17 * body in memory. This is used when neither | |
| 18 * {@link CronetHttpURLConnection#setFixedLengthStreamingMode} | |
| 19 * nor {@link CronetHttpURLConnection#setChunkedStreamingMode} is set. | |
| 20 */ | |
| 21 final class CronetBufferedOutputStream extends OutputStream | |
| 22 implements UploadDataProvider { | |
| 23 // If content length is not passed in the constructor, this is -1. | |
| 24 private final int mInitialContentLength; | |
| 25 private final CronetHttpURLConnection mConnection; | |
| 26 // Internal buffer that is used to buffer the request body. | |
| 27 private ByteBuffer mBuffer; | |
| 28 private boolean mConnected = false; | |
| 29 private boolean mFirstReadInitiated = false; | |
| 30 | |
| 31 /** | |
| 32 * Package protected constructor. | |
| 33 * @param connection The CronetHttpURLConnection object. | |
| 34 * @param contentLength The content length of the request body. It must not | |
| 35 * be smaller than 0 or bigger than {@link Integer.MAX_VALUE}. | |
| 36 */ | |
| 37 CronetBufferedOutputStream(final CronetHttpURLConnection connection, | |
| 38 final long contentLength) { | |
| 39 if (connection == null) { | |
| 40 throw new NullPointerException(); | |
| 41 } | |
| 42 | |
| 43 if (contentLength > Integer.MAX_VALUE) { | |
| 44 throw new IllegalStateException("Use setFixedLengthStreamingMode()" | |
| 45 + " or setChunkedStreamingMode() for requests larger than 2GB.") ; | |
| 46 } | |
| 47 if (contentLength < 0) { | |
| 48 throw new IllegalArgumentException("Content length < 0."); | |
| 49 } | |
| 50 mConnection = connection; | |
| 51 mInitialContentLength = (int) contentLength; | |
| 52 mBuffer = ByteBuffer.allocate(mInitialContentLength); | |
| 53 } | |
| 54 | |
| 55 /** | |
| 56 * Package protected constructor used when content length is not known. | |
| 57 * @param connection The CronetHttpURLConnection object. | |
| 58 */ | |
| 59 CronetBufferedOutputStream(final CronetHttpURLConnection connection) { | |
| 60 if (connection == null) { | |
| 61 throw new NullPointerException(); | |
| 62 } | |
| 63 | |
| 64 mConnection = connection; | |
| 65 mInitialContentLength = -1; | |
| 66 // Buffering without knowing content-length. | |
| 67 mBuffer = ByteBuffer.allocate(2048); | |
| 68 } | |
| 69 | |
| 70 @Override | |
| 71 public void write(int oneByte) throws IOException { | |
| 72 checkNotExceedContentLength(1); | |
| 73 if (mInitialContentLength == -1 && mBuffer.position() == mBuffer.limit() ) { | |
| 74 growInternalBuffer(1); | |
| 75 } | |
|
mmenke
2015/03/31 18:19:32
optional: Suggest moving this surrounding logic i
xunjieli
2015/04/01 17:04:08
Done. You are totally right! that does make the co
| |
| 76 mBuffer.put((byte) oneByte); | |
| 77 } | |
| 78 | |
| 79 @Override | |
| 80 public void write(byte[] buffer, int offset, int count) throws IOException { | |
| 81 checkNotExceedContentLength(count); | |
| 82 if (mInitialContentLength == -1 && mBuffer.limit() - mBuffer.position() < count) { | |
| 83 growInternalBuffer(count); | |
| 84 } | |
| 85 mBuffer.put(buffer, offset, count); | |
| 86 } | |
| 87 | |
| 88 /** | |
| 89 * Sets {@link #mConnected} to {@code true}. | |
| 90 */ | |
| 91 void setConnected() { | |
| 92 mConnected = true; | |
| 93 } | |
| 94 | |
| 95 // TODO(xunjieli): implement close(). | |
| 96 | |
| 97 /** | |
| 98 * Doubles the internal buffer or grows it at least by {@code count} bytes | |
| 99 * This method throws an IllegalStateException if the buffer | |
| 100 * is not meant to grow because content length is specified. | |
| 101 */ | |
| 102 private void growInternalBuffer(int count) { | |
| 103 if (mInitialContentLength != -1) { | |
| 104 throw new IllegalStateException("Buffer should not grow."); | |
| 105 } | |
| 106 int afterSize = Math.max(mBuffer.capacity() * 2, mBuffer.capacity() + co unt); | |
| 107 ByteBuffer newByteBuffer = ByteBuffer.allocate(afterSize); | |
| 108 mBuffer.flip(); | |
| 109 newByteBuffer.put(mBuffer); | |
| 110 mBuffer = newByteBuffer; | |
| 111 } | |
| 112 | |
| 113 /** | |
| 114 * Throws {@link java.net.ProtocolException} if adding {@code numBytes} will | |
| 115 * exceed content length. | |
| 116 */ | |
| 117 private void checkNotExceedContentLength(int numBytes) throws ProtocolExcept ion { | |
|
mmenke
2015/03/31 18:19:32
nit: checkContentLengthNotExceeded? Or could jus
xunjieli
2015/04/01 17:04:08
Done.thanks!
| |
| 118 if (mConnected) { | |
| 119 throw new IllegalStateException("Cannot write after being connected. "); | |
| 120 } | |
| 121 if (mInitialContentLength != -1 | |
| 122 && mBuffer.position() + numBytes > mInitialContentLength) { | |
| 123 // Error message is to match that of the default implementation. | |
| 124 throw new ProtocolException("exceeded content-length limit of " | |
| 125 + mInitialContentLength + " bytes"); | |
| 126 } | |
| 127 } | |
| 128 | |
| 129 // Below are UploadDataProvider implementations. Only intended to be used | |
| 130 // within Cronet. | |
| 131 | |
| 132 @Override | |
| 133 public long getLength() { | |
| 134 // This method is supposed to be called just before starting the request . | |
| 135 // If content length is not initially passed in, the number of bytes | |
| 136 // written will be used as the content length. | |
| 137 if (mInitialContentLength == -1) { | |
| 138 return mBuffer.position(); | |
| 139 } | |
| 140 return mInitialContentLength; | |
| 141 } | |
| 142 | |
| 143 @Override | |
| 144 public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) { | |
| 145 if (!mFirstReadInitiated) { | |
| 146 // This is the first read, so flip the buffer. | |
| 147 mBuffer.flip(); | |
| 148 mFirstReadInitiated = true; | |
| 149 } | |
|
mmenke
2015/03/31 18:19:32
Can we move this to setConnected, and get rid of m
xunjieli
2015/04/01 17:04:08
Done. Good idea!
| |
| 150 int oldPos = byteBuffer.position(); | |
| 151 int availableSpace = byteBuffer.capacity() - byteBuffer.position(); | |
| 152 if (availableSpace < mBuffer.limit() - mBuffer.position()) { | |
| 153 byteBuffer.put(mBuffer.array(), mBuffer.position(), availableSpace); | |
| 154 mBuffer.position(mBuffer.position() + availableSpace); | |
| 155 } else { | |
| 156 byteBuffer.put(mBuffer); | |
| 157 } | |
| 158 uploadDataSink.onReadSucceeded(false); | |
| 159 } | |
| 160 | |
| 161 @Override | |
| 162 public void rewind(UploadDataSink uploadDataSink) { | |
| 163 mBuffer.position(0); | |
| 164 uploadDataSink.onRewindSucceeded(); | |
| 165 } | |
| 166 } | |
| OLD | NEW |