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 * Note that {@link #write} should only be called from the thread on which the | |
21 * {@link #mConnection} is created. | |
mmenke
2015/03/25 18:34:57
This should mention "fixedStreamingMode" or "fixed
xunjieli
2015/03/27 17:46:45
Done.
| |
22 */ | |
23 final class CronetOutputStream extends OutputStream implements UploadDataProvide r { | |
mmenke
2015/03/25 18:34:57
Maybe rename to something like CronetFixedModeOutp
xunjieli
2015/03/27 17:46:45
Done.
| |
24 // CronetOutputStream buffers up to this value and wait for UploadDataStream | |
25 // to consume the data. This field is non-final, so it can be changed for te sts. | |
26 // TODO(xunjieli): figure out whether this default value should be changed. | |
27 @VisibleForTesting | |
28 private static int sDefaultBufferLength = 2048; | |
29 private final CronetHttpURLConnection mConnection; | |
30 private final long mContentLength; | |
31 private final ByteBuffer mBuffer; | |
32 private long mBytesReceived; | |
33 | |
34 /** | |
35 * Package protected constructor. | |
36 * @param connection The CronetHttpURLConnection object. | |
37 * @param contentLength The content length of the request body. Non-zero for | |
38 * non-chunked upload. | |
39 */ | |
40 CronetOutputStream(CronetHttpURLConnection connection, long contentLength) { | |
41 if (connection == null) { | |
42 throw new NullPointerException(); | |
43 } | |
44 if (contentLength < 0) { | |
45 throw new IllegalArgumentException( | |
46 "Content length must be larger than 0 for non-chunked upload ."); | |
47 } | |
48 mContentLength = contentLength; | |
49 int bufferSize = (int) Math.min(mContentLength, sDefaultBufferLength); | |
50 mBuffer = ByteBuffer.allocate(bufferSize); | |
51 mConnection = connection; | |
52 mBytesReceived = 0; | |
53 } | |
54 | |
55 @Override | |
56 public void write(int oneByte) throws IOException { | |
57 checkNotExceedContentLength(1); | |
58 while (mBuffer.position() == mBuffer.limit()) { | |
59 // Wait until buffer is consumed. | |
60 mConnection.waitForRead(); | |
61 } | |
62 mBuffer.put((byte) oneByte); | |
63 mBytesReceived++; | |
64 if (mBytesReceived == mContentLength) { | |
65 // Entire post data has been received. Now wait for network stack to | |
66 // read it. | |
67 mConnection.waitForRead(); | |
68 } | |
69 } | |
70 | |
71 @Override | |
72 public void write(byte[] buffer, int offset, int count) throws IOException { | |
73 if (buffer.length - offset < count || offset < 0 || count < 0) { | |
74 throw new IndexOutOfBoundsException(); | |
75 } | |
76 checkNotExceedContentLength(count); | |
77 if (count == 0) { | |
78 return; | |
79 } | |
80 int toSend = count; | |
81 while (toSend > 0) { | |
82 if (mBuffer.position() == mBuffer.limit()) { | |
83 // Wait until buffer is consumed. | |
84 mConnection.waitForRead(); | |
85 } | |
86 int sent = Math.min(toSend, mBuffer.limit() - mBuffer.position()); | |
87 mBuffer.put(buffer, offset + count - toSend, sent); | |
88 toSend -= sent; | |
89 } | |
90 mBytesReceived += count; | |
91 if (mBytesReceived == mContentLength) { | |
92 // Entire post data has been received. Now wait for network stack to | |
93 // read it. | |
94 mConnection.waitForRead(); | |
95 } | |
96 } | |
97 | |
98 // TODO(xunjieli): implement close(). | |
99 | |
100 /** | |
101 * Throws {@link java.net.ProtocolException} if adding {@code numBytes} will | |
102 * exceed content length. | |
103 */ | |
104 private void checkNotExceedContentLength(int numBytes) throws ProtocolExcept ion { | |
105 if (mContentLength != -1 && mBytesReceived + numBytes > mContentLength) { | |
106 throw new ProtocolException("expected " | |
107 + (mContentLength - mBytesReceived) + " bytes but received " | |
108 + numBytes); | |
109 } | |
110 } | |
111 | |
112 // Below are UploadDataProvider implementations. Only intended to be used | |
113 // within Cronet. | |
114 | |
115 @Override | |
116 public long getLength() { | |
117 return mContentLength; | |
118 } | |
119 | |
120 @Override | |
121 public void read(final UploadDataSink uploadDataSink, final ByteBuffer byteB uffer) { | |
122 Runnable readTask = new Runnable() { | |
mmenke
2015/03/25 18:34:57
Can't we just inline this instead of making it a t
xunjieli
2015/03/27 17:46:45
Done. I ran into problem with loop() being invoked
| |
123 @Override | |
124 public void run() { | |
125 if (byteBuffer.remaining() < mBuffer.position()) { | |
126 // byteBuffer does not have enough capacity, so only put a p ortion | |
127 // of mBuffer in it. | |
128 mBuffer.position(byteBuffer.remaining()); | |
129 byteBuffer.put(mBuffer.array(), 0, byteBuffer.remaining()); | |
130 // Move remaining buffer to the head of the buffer for use i n the | |
131 // next read call. | |
132 mBuffer.compact(); | |
133 } else { | |
134 // byteBuffer has enough capacity to hold content in mBuffer . | |
135 mBuffer.flip(); | |
136 byteBuffer.put(mBuffer); | |
137 // Reuse this buffer. | |
138 mBuffer.clear(); | |
139 } | |
140 uploadDataSink.onReadSucceeded(false); | |
141 } | |
142 }; | |
143 if (mBuffer.position() == 0) { | |
144 // Exit the message loop, so consumer can write more data. | |
145 if (mBytesReceived < mContentLength) { | |
146 mConnection.postponeRead(readTask); | |
147 } | |
148 } else { | |
149 readTask.run(); | |
150 } | |
151 } | |
152 | |
153 @Override | |
154 public void rewind(UploadDataSink uploadDataSink) { | |
155 uploadDataSink.onRewindError(null); | |
mmenke
2015/03/25 18:34:57
Passing a null exception along seems like a bad id
xunjieli
2015/03/27 17:46:45
Done.
| |
156 } | |
157 | |
158 /** | |
159 * Sets the default buffer length for use in tests. | |
160 */ | |
161 @VisibleForTesting | |
162 static void setDefaultBufferLengthForTesting(int length) { | |
163 sDefaultBufferLength = length; | |
164 } | |
165 } | |
OLD | NEW |