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 * It does not support rewind. Note that {@link #write} should only be called | |
| 21 * from the thread on which the {@link #mConnection} is created. | |
| 22 */ | |
| 23 final class CronetFixedModeOutputStream extends OutputStream | |
| 24 implements UploadDataProvider { | |
| 25 // CronetFixedModeOutputStream buffers up to this value and wait for UploadD ataStream | |
| 26 // to consume the data. This field is non-final, so it can be changed for te sts. | |
| 27 // TODO(xunjieli): figure out whether this default value should be changed. | |
|
mef
2015/04/06 16:14:54
I'd suggest 32k.
xunjieli
2015/04/06 18:09:29
I don't think it should be 32k. QUIC uses a upload
| |
| 28 @VisibleForTesting | |
| 29 private static int sDefaultBufferLength = 2048; | |
| 30 private final CronetHttpURLConnection mConnection; | |
| 31 private final MessageLoop mMessageLoop; | |
| 32 private final long mContentLength; | |
| 33 private final ByteBuffer mBuffer; | |
| 34 private long mBytesReceived; | |
| 35 | |
| 36 /** | |
| 37 * Package protected constructor. | |
| 38 * @param connection The CronetHttpURLConnection object. | |
| 39 * @param contentLength The content length of the request body. Non-zero for | |
| 40 * non-chunked upload. | |
| 41 */ | |
| 42 CronetFixedModeOutputStream(CronetHttpURLConnection connection, | |
| 43 long contentLength, MessageLoop messageLoop) { | |
| 44 if (connection == null) { | |
| 45 throw new NullPointerException(); | |
| 46 } | |
| 47 if (contentLength < 0) { | |
| 48 throw new IllegalArgumentException( | |
| 49 "Content length must be larger than 0 for non-chunked upload ."); | |
| 50 } | |
| 51 mContentLength = contentLength; | |
| 52 int bufferSize = (int) Math.min(mContentLength, sDefaultBufferLength); | |
| 53 mBuffer = ByteBuffer.allocate(bufferSize); | |
| 54 mConnection = connection; | |
| 55 mMessageLoop = messageLoop; | |
| 56 mBytesReceived = 0; | |
| 57 } | |
| 58 | |
| 59 @Override | |
| 60 public void write(int oneByte) throws IOException { | |
| 61 checkNotExceedContentLength(1); | |
| 62 while (mBuffer.position() == mBuffer.limit()) { | |
| 63 // Wait until buffer is consumed. | |
| 64 mMessageLoop.loop(); | |
| 65 } | |
| 66 mBuffer.put((byte) oneByte); | |
| 67 mBytesReceived++; | |
|
mef
2015/04/06 16:14:54
Rename mBytesReceived => mBytesWritten?
Not sure a
xunjieli
2015/04/06 18:09:29
Done. I believe the increment should be after the
mef
2015/04/06 18:37:18
sg
| |
| 68 if (mBytesReceived == mContentLength) { | |
| 69 // Entire post data has been received. Now wait for network stack to | |
| 70 // read it. | |
| 71 mMessageLoop.loop(); | |
| 72 } | |
| 73 } | |
| 74 | |
| 75 @Override | |
| 76 public void write(byte[] buffer, int offset, int count) throws IOException { | |
| 77 if (buffer.length - offset < count || offset < 0 || count < 0) { | |
| 78 throw new IndexOutOfBoundsException(); | |
| 79 } | |
| 80 checkNotExceedContentLength(count); | |
| 81 if (count == 0) { | |
| 82 return; | |
| 83 } | |
| 84 int toSend = count; | |
| 85 while (toSend > 0) { | |
| 86 if (mBuffer.position() == mBuffer.limit()) { | |
| 87 // Wait until buffer is consumed. | |
| 88 mMessageLoop.loop(); | |
| 89 } | |
| 90 int sent = Math.min(toSend, mBuffer.limit() - mBuffer.position()); | |
| 91 mBuffer.put(buffer, offset + count - toSend, sent); | |
| 92 toSend -= sent; | |
| 93 } | |
| 94 mBytesReceived += count; | |
| 95 if (mBytesReceived == mContentLength) { | |
| 96 // Entire post data has been received. Now wait for network stack to | |
| 97 // read it. | |
| 98 mMessageLoop.loop(); | |
| 99 } | |
| 100 } | |
| 101 | |
| 102 /** | |
| 103 * Checks whether content received is less than Content-Length. | |
| 104 */ | |
| 105 void checkReceivedEnoughContent() throws IOException { | |
|
mef
2015/04/06 16:14:54
not used?
xunjieli
2015/04/06 18:09:29
Used in CronetHttpURLConnection's startRequest() t
mef
2015/04/06 18:37:18
Ah, I see, maybe add comment? Not sure.
xunjieli
2015/04/06 21:03:45
Done. Right, this should deserve a comment.
| |
| 106 if (mBytesReceived < mContentLength) { | |
| 107 throw new ProtocolException("Content received is less than Content-L ength."); | |
| 108 } | |
| 109 } | |
| 110 | |
| 111 // TODO(xunjieli): implement close(). | |
| 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 { | |
| 118 if (mContentLength != -1 && mBytesReceived + numBytes > mContentLength) { | |
|
mef
2015/04/06 16:14:54
When mContentLength could == -1?
xunjieli
2015/04/06 18:09:29
Done. Good catch!
| |
| 119 throw new ProtocolException("expected " | |
| 120 + (mContentLength - mBytesReceived) + " bytes but received " | |
| 121 + numBytes); | |
| 122 } | |
| 123 } | |
| 124 | |
| 125 // Below are UploadDataProvider implementations. Only intended to be used | |
| 126 // within Cronet. | |
| 127 | |
| 128 @Override | |
| 129 public long getLength() { | |
| 130 return mContentLength; | |
| 131 } | |
| 132 | |
| 133 @Override | |
| 134 public void read(final UploadDataSink uploadDataSink, final ByteBuffer byteB uffer) { | |
| 135 int availableSpace = byteBuffer.capacity() - byteBuffer.position(); | |
| 136 if (availableSpace < mBuffer.position()) { | |
| 137 // byteBuffer does not have enough capacity, so only put a portion | |
| 138 // of mBuffer in it. | |
| 139 byteBuffer.put(mBuffer.array(), 0, availableSpace); | |
| 140 mBuffer.position(availableSpace); | |
| 141 // Move remaining buffer to the head of the buffer for use in the | |
| 142 // next read call. | |
| 143 mBuffer.compact(); | |
|
mef
2015/04/06 16:14:54
moving memory is not cheap, is there a way to avoi
xunjieli
2015/04/06 18:09:29
I don't think it will be a problem if our buffer s
| |
| 144 } else { | |
| 145 // byteBuffer has enough capacity to hold the content of mBuffer. | |
| 146 mBuffer.flip(); | |
| 147 byteBuffer.put(mBuffer); | |
| 148 // Reuse this buffer. | |
| 149 mBuffer.clear(); | |
| 150 // Quit message loop so embedder can write more data. | |
| 151 mMessageLoop.postQuitTask(); | |
| 152 } | |
| 153 uploadDataSink.onReadSucceeded(false); | |
| 154 } | |
| 155 | |
| 156 @Override | |
| 157 public void rewind(UploadDataSink uploadDataSink) { | |
| 158 uploadDataSink.onRewindError(new IllegalStateException( | |
| 159 "Rewind is not supported by CronetFixedModeOutputStream.")); | |
| 160 } | |
| 161 | |
| 162 /** | |
| 163 * Sets the default buffer length for use in tests. | |
| 164 */ | |
| 165 @VisibleForTesting | |
| 166 static void setDefaultBufferLengthForTesting(int length) { | |
| 167 sDefaultBufferLength = length; | |
| 168 } | |
| 169 } | |
| OLD | NEW |