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(); | |
mef
2015/04/06 16:14:54
add error message?
xunjieli
2015/04/06 18:09:29
Done.
| |
41 } | |
42 | |
43 if (contentLength > Integer.MAX_VALUE) { | |
44 throw new IllegalStateException("Use setFixedLengthStreamingMode()" | |
mef
2015/04/06 16:14:54
I would throw IllegalArgumentException here for co
xunjieli
2015/04/06 18:09:29
Done.
| |
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); | |
mef
2015/04/06 16:14:54
Define 2048 as constant? While there, should it be
xunjieli
2015/04/06 18:09:29
Hmm.. 2048 isn't used anywhere else in this file,
mef
2015/04/06 18:37:18
16k sgtm. My concern is the need for reallocs/copi
xunjieli
2015/04/06 21:03:45
Done. I don't know which one is better. I guess we
| |
68 } | |
69 | |
70 @Override | |
71 public void write(int oneByte) throws IOException { | |
72 ensureCanWrite(1); | |
73 mBuffer.put((byte) oneByte); | |
74 } | |
75 | |
76 @Override | |
77 public void write(byte[] buffer, int offset, int count) throws IOException { | |
78 ensureCanWrite(count); | |
79 mBuffer.put(buffer, offset, count); | |
80 } | |
81 | |
82 /** | |
83 * Sets {@link #mConnected} to {@code true}. | |
84 */ | |
85 void setConnected() throws IOException { | |
86 mConnected = true; | |
87 if (mBuffer.position() < mInitialContentLength) { | |
88 throw new ProtocolException("Content received is less than Content-L ength"); | |
89 } | |
90 // Flip the buffer to prepare it for UploadDataProvider read calls. | |
91 mBuffer.flip(); | |
92 } | |
93 | |
94 // TODO(xunjieli): implement close(). | |
95 | |
96 /** | |
97 * Ensures that {@code count} bytes can be written to the internal buffer. | |
98 */ | |
99 private void ensureCanWrite(int count) throws IOException { | |
100 if (mInitialContentLength != -1 | |
101 && mBuffer.position() + count > mInitialContentLength) { | |
102 // Error message is to match that of the default implementation. | |
103 throw new ProtocolException("exceeded content-length limit of " | |
104 + mInitialContentLength + " bytes"); | |
105 } | |
106 if (mConnected) { | |
107 throw new IllegalStateException("Cannot write after being connected. "); | |
108 } | |
109 if (mInitialContentLength != -1 || mBuffer.limit() - mBuffer.position() > count) { | |
mef
2015/04/06 16:14:54
Can we split it into 2 separate checks?
Also mayb
xunjieli
2015/04/06 18:09:29
Done.
| |
110 // If mInitialContentLength is known or there is enough capacity, | |
111 // the buffer should not grow. | |
112 return; | |
113 } | |
114 int afterSize = Math.max(mBuffer.capacity() * 2, mBuffer.capacity() + co unt); | |
115 ByteBuffer newByteBuffer = ByteBuffer.allocate(afterSize); | |
116 mBuffer.flip(); | |
117 newByteBuffer.put(mBuffer); | |
118 mBuffer = newByteBuffer; | |
119 } | |
120 | |
121 // Below are UploadDataProvider implementations. Only intended to be used | |
122 // within Cronet. | |
123 | |
124 @Override | |
125 public long getLength() { | |
126 // This method is supposed to be called just before starting the request . | |
127 // If content length is not initially passed in, the number of bytes | |
128 // written will be used as the content length. | |
129 if (mInitialContentLength == -1) { | |
130 return mBuffer.position(); | |
mef
2015/04/06 16:14:54
hrm, this will be 0 if rewind() is called.
xunjieli
2015/04/06 18:09:29
getLength() is called in CronetUploadDataStream's
mef
2015/04/06 18:37:18
My concern here is fragility, few years from now s
xunjieli
2015/04/06 21:03:45
If we have a buffer of 2048 bytes, and we write 18
| |
131 } | |
132 return mInitialContentLength; | |
133 } | |
134 | |
135 @Override | |
136 public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) { | |
137 int oldPos = byteBuffer.position(); | |
138 int availableSpace = byteBuffer.capacity() - byteBuffer.position(); | |
mef
2015/04/06 16:14:54
Maybe rename availableSpace => readSize?
xunjieli
2015/04/06 18:09:29
It's actually not the read size, since the else cl
mef
2015/04/06 18:37:18
ok
| |
139 if (availableSpace < mBuffer.limit() - mBuffer.position()) { | |
140 byteBuffer.put(mBuffer.array(), mBuffer.position(), availableSpace); | |
141 mBuffer.position(mBuffer.position() + availableSpace); | |
142 } else { | |
143 byteBuffer.put(mBuffer); | |
144 } | |
145 uploadDataSink.onReadSucceeded(false); | |
146 } | |
147 | |
148 @Override | |
149 public void rewind(UploadDataSink uploadDataSink) { | |
150 mBuffer.position(0); | |
151 uploadDataSink.onRewindSucceeded(); | |
152 } | |
153 } | |
OLD | NEW |