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 |