Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(933)

Side by Side Diff: components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLConnection.java

Issue 966743003: [Cronet] Implement getOutputStream in CronetHttpURLConnection (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@chunked_support
Patch Set: Test write after connect for fixed length streaming mode Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package org.chromium.net.urlconnection; 5 package org.chromium.net.urlconnection;
6 6
7 import android.util.Log;
7 import android.util.Pair; 8 import android.util.Pair;
8 9
9 import org.chromium.net.ExtendedResponseInfo; 10 import org.chromium.net.ExtendedResponseInfo;
10 import org.chromium.net.ResponseInfo; 11 import org.chromium.net.ResponseInfo;
12 import org.chromium.net.UploadDataProvider;
11 import org.chromium.net.UrlRequest; 13 import org.chromium.net.UrlRequest;
12 import org.chromium.net.UrlRequestContext; 14 import org.chromium.net.UrlRequestContext;
13 import org.chromium.net.UrlRequestException; 15 import org.chromium.net.UrlRequestException;
14 import org.chromium.net.UrlRequestListener; 16 import org.chromium.net.UrlRequestListener;
15 17
16 import java.io.FileNotFoundException; 18 import java.io.FileNotFoundException;
17 import java.io.IOException; 19 import java.io.IOException;
18 import java.io.InputStream; 20 import java.io.InputStream;
21 import java.io.OutputStream;
19 import java.net.HttpURLConnection; 22 import java.net.HttpURLConnection;
20 import java.net.MalformedURLException; 23 import java.net.MalformedURLException;
24 import java.net.ProtocolException;
21 import java.net.URL; 25 import java.net.URL;
22 import java.nio.ByteBuffer; 26 import java.nio.ByteBuffer;
23 import java.util.ArrayList; 27 import java.util.ArrayList;
24 import java.util.Collections; 28 import java.util.Collections;
25 import java.util.List; 29 import java.util.List;
26 import java.util.Map; 30 import java.util.Map;
27 import java.util.TreeMap; 31 import java.util.TreeMap;
28 32
29 /** 33 /**
30 * An implementation of HttpURLConnection that uses Cronet to send requests and 34 * An implementation of HttpURLConnection that uses Cronet to send requests and
31 * receive response. This class inherits a {@code connected} field from the 35 * receive response. This class inherits a {@code connected} field from the
32 * superclass. That field indicates whether a connection has ever been 36 * superclass. That field indicates whether a connection has ever been
33 * attempted. 37 * attempted.
34 */ 38 */
35 public class CronetHttpURLConnection extends HttpURLConnection { 39 public class CronetHttpURLConnection extends HttpURLConnection {
40 private static final String TAG = "CronetHttpURLConnection";
36 private final UrlRequestContext mUrlRequestContext; 41 private final UrlRequestContext mUrlRequestContext;
37 private final MessageLoop mMessageLoop; 42 private final MessageLoop mMessageLoop;
38 private final UrlRequest mRequest; 43 private final UrlRequest mRequest;
39 private final List<Pair<String, String>> mRequestHeaders; 44 private final List<Pair<String, String>> mRequestHeaders;
40 45
41 private CronetInputStream mInputStream; 46 private CronetInputStream mInputStream;
47 private OutputStream mOutputStream;
42 private ResponseInfo mResponseInfo; 48 private ResponseInfo mResponseInfo;
43 private UrlRequestException mException; 49 private UrlRequestException mException;
44 private ByteBuffer mResponseByteBuffer; 50 private ByteBuffer mResponseByteBuffer;
45 private boolean mOnRedirectCalled = false; 51 private boolean mOnRedirectCalled = false;
52 private boolean mHasResponse = false;
46 53
47 protected CronetHttpURLConnection(URL url, 54 public CronetHttpURLConnection(URL url,
48 UrlRequestContext urlRequestContext) { 55 UrlRequestContext urlRequestContext) {
49 super(url); 56 super(url);
50 mUrlRequestContext = urlRequestContext; 57 mUrlRequestContext = urlRequestContext;
51 mMessageLoop = new MessageLoop(); 58 mMessageLoop = new MessageLoop();
52 mRequest = mUrlRequestContext.createRequest(url.toString(), 59 mRequest = mUrlRequestContext.createRequest(url.toString(),
53 new CronetUrlRequestListener(), mMessageLoop); 60 new CronetUrlRequestListener(), mMessageLoop);
54 mInputStream = new CronetInputStream(this); 61 mInputStream = new CronetInputStream(this);
55 mRequestHeaders = new ArrayList<Pair<String, String>>(); 62 mRequestHeaders = new ArrayList<Pair<String, String>>();
56 } 63 }
57 64
58 /** 65 /**
59 * Opens a connection to the resource. If the connect method is called when 66 * Opens a connection to the resource. If the connect method is called when
60 * the connection has already been opened (indicated by the connected field 67 * the connection has already been opened (indicated by the connected field
61 * having the value true), the call is ignored unless an exception is thrown 68 * having the value true), the call is ignored.
62 * previously, in which case, the exception will be rethrown.
63 */ 69 */
64 @Override 70 @Override
65 public void connect() throws IOException { 71 public void connect() throws IOException {
66 if (connected) { 72 startRequest();
67 checkHasResponse();
68 return;
69 }
70 connected = true;
71 for (Pair<String, String> requestHeader : mRequestHeaders) {
72 mRequest.addHeader(requestHeader.first, requestHeader.second);
73 }
74 if (!getUseCaches()) {
75 mRequest.disableCache();
76 }
77 mRequest.start();
78 // Blocks until onResponseStarted or onFailed is called.
79 mMessageLoop.loop();
80 checkHasResponse();
81 } 73 }
82 74
83 /** 75 /**
84 * Releases this connection so that its resources may be either reused or 76 * Releases this connection so that its resources may be either reused or
85 * closed. 77 * closed.
86 */ 78 */
87 @Override 79 @Override
88 public void disconnect() { 80 public void disconnect() {
89 // Disconnect before connection is made should have no effect. 81 // Disconnect before connection is made should have no effect.
90 if (connected) { 82 if (connected && mInputStream != null) {
91 try { 83 try {
92 mInputStream.close(); 84 mInputStream.close();
93 } catch (IOException e) { 85 } catch (IOException e) {
94 e.printStackTrace(); 86 e.printStackTrace();
95 } 87 }
96 mInputStream = null; 88 mInputStream = null;
97 mRequest.cancel(); 89 mRequest.cancel();
98 } 90 }
99 } 91 }
100 92
101 /** 93 /**
102 * Returns the response message returned by the remote HTTP server. 94 * Returns the response message returned by the remote HTTP server.
103 */ 95 */
104 @Override 96 @Override
105 public String getResponseMessage() throws IOException { 97 public String getResponseMessage() throws IOException {
106 connect(); 98 getResponse();
107 return mResponseInfo.getHttpStatusText(); 99 return mResponseInfo.getHttpStatusText();
108 } 100 }
109 101
110 /** 102 /**
111 * Returns the response code returned by the remote HTTP server. 103 * Returns the response code returned by the remote HTTP server.
112 */ 104 */
113 @Override 105 @Override
114 public int getResponseCode() throws IOException { 106 public int getResponseCode() throws IOException {
115 connect(); 107 getResponse();
116 return mResponseInfo.getHttpStatusCode(); 108 return mResponseInfo.getHttpStatusCode();
117 } 109 }
118 110
119 /** 111 /**
120 * Returns an unmodifiable map of the response-header fields and values. 112 * Returns an unmodifiable map of the response-header fields and values.
121 */ 113 */
122 @Override 114 @Override
123 public Map<String, List<String>> getHeaderFields() { 115 public Map<String, List<String>> getHeaderFields() {
124 try { 116 try {
125 connect(); 117 getResponse();
126 } catch (IOException e) { 118 } catch (IOException e) {
127 return Collections.emptyMap(); 119 return Collections.emptyMap();
128 } 120 }
129 return mResponseInfo.getAllHeaders(); 121 return mResponseInfo.getAllHeaders();
130 } 122 }
131 123
132 /** 124 /**
133 * Returns the value of the named header field. If called on a connection 125 * Returns the value of the named header field. If called on a connection
134 * that sets the same header multiple times with possibly different values, 126 * that sets the same header multiple times with possibly different values,
135 * only the last value is returned. 127 * only the last value is returned.
136 */ 128 */
137 @Override 129 @Override
138 public final String getHeaderField(String fieldName) { 130 public final String getHeaderField(String fieldName) {
139 try { 131 try {
140 connect(); 132 getResponse();
141 } catch (IOException e) { 133 } catch (IOException e) {
142 return null; 134 return null;
143 } 135 }
144 Map<String, List<String>> map = mResponseInfo.getAllHeaders(); 136 Map<String, List<String>> map = mResponseInfo.getAllHeaders();
145 if (!map.containsKey(fieldName)) { 137 if (!map.containsKey(fieldName)) {
146 return null; 138 return null;
147 } 139 }
148 List<String> values = map.get(fieldName); 140 List<String> values = map.get(fieldName);
149 return values.get(values.size() - 1); 141 return values.get(values.size() - 1);
150 } 142 }
(...skipping 28 matching lines...) Expand all
179 * Returns an InputStream for reading data from the resource pointed by this 171 * Returns an InputStream for reading data from the resource pointed by this
180 * URLConnection. 172 * URLConnection.
181 * @throws FileNotFoundException if http response code is equal or greater 173 * @throws FileNotFoundException if http response code is equal or greater
182 * than {@link HTTP_BAD_REQUEST}. 174 * than {@link HTTP_BAD_REQUEST}.
183 * @throws IOException If the request gets a network error or HTTP error 175 * @throws IOException If the request gets a network error or HTTP error
184 * status code, or if the caller tried to read the response body 176 * status code, or if the caller tried to read the response body
185 * of a redirect when redirects are disabled. 177 * of a redirect when redirects are disabled.
186 */ 178 */
187 @Override 179 @Override
188 public InputStream getInputStream() throws IOException { 180 public InputStream getInputStream() throws IOException {
189 connect(); 181 getResponse();
190 if (!instanceFollowRedirects && mOnRedirectCalled) { 182 if (!instanceFollowRedirects && mOnRedirectCalled) {
191 throw new IOException("Cannot read response body of a redirect."); 183 throw new IOException("Cannot read response body of a redirect.");
192 } 184 }
193 // Emulate default implementation's behavior to throw 185 // Emulate default implementation's behavior to throw
194 // FileNotFoundException when we get a 400 and above. 186 // FileNotFoundException when we get a 400 and above.
195 if (mResponseInfo.getHttpStatusCode() >= HTTP_BAD_REQUEST) { 187 if (mResponseInfo.getHttpStatusCode() >= HTTP_BAD_REQUEST) {
196 throw new FileNotFoundException(url.toString()); 188 throw new FileNotFoundException(url.toString());
197 } 189 }
198 return mInputStream; 190 return mInputStream;
199 } 191 }
200 192
193 @Override
194 public OutputStream getOutputStream() throws IOException {
195 if (mOutputStream == null) {
196 if (connected) {
197 throw new ProtocolException(
198 "Cannot write to OutputStream after receiving response." );
199 }
200 long fixedStreamingModeContentLength = getStreamingModeContentLength ();
201 if (fixedStreamingModeContentLength != -1) {
202 mOutputStream = new CronetFixedModeOutputStream(this,
203 fixedStreamingModeContentLength, mMessageLoop);
204 // Start the request now since all headers can be sent.
mef 2015/04/06 16:14:55 why is this true?
xunjieli 2015/04/06 18:09:29 Because the user sets fixedLengthStreamingMode, so
mef 2015/04/06 18:37:18 Yes, but why couldn't user call setRequestProperty
xunjieli 2015/04/06 21:03:45 getOutputStream() should try to establish a connec
205 startRequest();
206 } else {
207 // For the buffered case, start the request only when
208 // content-length bytes are received, or when a
209 // connect action is initiated by the consumer.
210 Log.d(TAG, "Outputstream is being buffered in memory.");
211 String length = getRequestProperty("Content-Length");
212 if (length == null) {
213 mOutputStream = new CronetBufferedOutputStream(this);
214 } else {
215 long lengthParsed = Long.parseLong(length);
mef 2015/04/06 16:14:54 what if it is over 2gb?
xunjieli 2015/04/06 18:09:29 CronetBufferedOutputStream's constructor will thro
216 mOutputStream = new CronetBufferedOutputStream(this, lengthP arsed);
217 }
218 }
219 }
220 return mOutputStream;
221 }
222
223 /**
224 * Helper method to get content length passed in by
225 * {@link #setFixedLengthStreamingMode}
226 */
227 private long getStreamingModeContentLength() {
228 long contentLength = fixedContentLength;
229 // Use reflection to see whether fixedContentLengthLong (only added
230 // in API 19) is inherited.
231 try {
232 Class<?> parent = this.getClass();
233 long superFixedContentLengthLong =
234 parent.getField("fixedContentLengthLong").getLong(this);
235 if (superFixedContentLengthLong != -1) {
236 contentLength = superFixedContentLengthLong;
237 }
238 } catch (Exception e) {
239 // Ignored.
240 }
241 return contentLength;
242 }
243
244 /**
245 * Starts the request if {@code connected} is false.
246 */
247 private void startRequest() throws IOException {
248 if (connected) {
249 return;
250 }
251 if (doOutput) {
252 if (mOutputStream != null) {
253 mRequest.setUploadDataProvider(
254 (UploadDataProvider) mOutputStream, mMessageLoop);
255 if (getRequestProperty("Content-Length") == null) {
256 addRequestProperty("Content-Length",
257 Long.toString(((UploadDataProvider) mOutputStream).g etLength()));
258 }
259 if (mOutputStream instanceof CronetBufferedOutputStream) {
260 // Disallow the embedder to write more data, and prepare
261 // internal buffer for reading.
262 ((CronetBufferedOutputStream) mOutputStream).setConnected();
mef 2015/04/06 16:14:54 can mOutputStream be a common base class like 'Cro
xunjieli 2015/04/06 18:09:29 Only CronetBufferedOutputStream needs this method.
263 }
264 } else {
265 if (getRequestProperty("Content-Length") == null) {
mef 2015/04/06 16:14:55 suggest string constant for "Content-Length" and "
xunjieli 2015/04/06 18:09:30 Done.
266 addRequestProperty("Content-Length", "0");
267 }
268 }
269 // Default Content-Type to application/x-www-form-urlencoded
270 if (getRequestProperty("Content-Type") == null) {
271 addRequestProperty("Content-Type",
272 "application/x-www-form-urlencoded");
273 }
274 }
275 for (Pair<String, String> requestHeader : mRequestHeaders) {
276 mRequest.addHeader(requestHeader.first, requestHeader.second);
277 }
278 if (!getUseCaches()) {
279 mRequest.disableCache();
280 }
281 connected = true;
282 // Start the request.
283 mRequest.start();
284 }
285
201 /** 286 /**
202 * Returns an input stream from the server in the case of an error such as 287 * Returns an input stream from the server in the case of an error such as
203 * the requested file has not been found on the remote server. 288 * the requested file has not been found on the remote server.
204 */ 289 */
205 @Override 290 @Override
206 public InputStream getErrorStream() { 291 public InputStream getErrorStream() {
207 try { 292 try {
208 connect(); 293 getResponse();
209 } catch (IOException e) { 294 } catch (IOException e) {
210 return null; 295 return null;
211 } 296 }
212 if (mResponseInfo.getHttpStatusCode() >= HTTP_BAD_REQUEST) { 297 if (mResponseInfo.getHttpStatusCode() >= HTTP_BAD_REQUEST) {
213 return mInputStream; 298 return mInputStream;
214 } 299 }
215 return null; 300 return null;
216 } 301 }
217 302
218 /** 303 /**
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after
294 /** 379 /**
295 * Returns whether this connection uses a proxy server. 380 * Returns whether this connection uses a proxy server.
296 */ 381 */
297 @Override 382 @Override
298 public boolean usingProxy() { 383 public boolean usingProxy() {
299 // TODO(xunjieli): implement this. 384 // TODO(xunjieli): implement this.
300 return false; 385 return false;
301 } 386 }
302 387
303 /** 388 /**
389 * Sets chunked streaming mode.
390 */
391 @Override
392 public void setChunkedStreamingMode(int chunklen) {
393 // TODO(xunjieli): implement this.
394 throw new UnsupportedOperationException("Chunked mode not supported yet" );
395 }
396
397 /**
304 * Used by {@link CronetInputStream} to get more data from the network 398 * Used by {@link CronetInputStream} to get more data from the network
305 * stack. This should only be called after the request has started. Note 399 * stack. This should only be called after the request has started. Note
306 * that this call might block if there isn't any more data to be read. 400 * that this call might block if there isn't any more data to be read.
307 */ 401 */
308 ByteBuffer getMoreData() throws IOException { 402 ByteBuffer getMoreData() throws IOException {
309 mResponseByteBuffer = null; 403 mResponseByteBuffer = null;
310 mMessageLoop.loop(); 404 mMessageLoop.loop();
311 return mResponseByteBuffer; 405 return mResponseByteBuffer;
312 } 406 }
313 407
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
387 */ 481 */
388 private void setResponseDataCompleted() { 482 private void setResponseDataCompleted() {
389 if (mInputStream != null) { 483 if (mInputStream != null) {
390 mInputStream.setResponseDataCompleted(); 484 mInputStream.setResponseDataCompleted();
391 } 485 }
392 mMessageLoop.postQuitTask(); 486 mMessageLoop.postQuitTask();
393 } 487 }
394 } 488 }
395 489
396 /** 490 /**
491 * Blocks until the respone headers are received.
492 */
493 private void getResponse() throws IOException {
494 // Check to see if enough data has been received. CronetBufferedOutputSt ream's
495 // case is checked in CronetBufferedOutputStream#setConnected().
496 if (mOutputStream != null && mOutputStream instanceof CronetFixedModeOut putStream) {
497 ((CronetFixedModeOutputStream) mOutputStream).checkReceivedEnoughCon tent();
mef 2015/04/06 16:14:54 Maybe make checkReceivedEnoughContent a base class
xunjieli 2015/04/06 18:09:29 Only CronetFixedModeOutputStream needs this method
mef 2015/04/06 18:37:18 But it can be an abstract class that extends Outpu
xunjieli 2015/04/06 21:03:45 Done. Yes, you are right. Changed the code to do t
498 }
499 if (!mHasResponse) {
500 startRequest();
501 // Blocks until onResponseStarted or onFailed is called.
502 mMessageLoop.loop();
503 mHasResponse = true;
504 }
505 checkHasResponse();
506 }
507
508 /**
397 * Checks whether response headers are received, and throws an exception if 509 * Checks whether response headers are received, and throws an exception if
398 * an exception occurred before headers received. This method should only be 510 * an exception occurred before headers received. This method should only be
399 * called after onResponseStarted or onFailed. 511 * called after onResponseStarted or onFailed.
400 */ 512 */
401 private void checkHasResponse() throws IOException { 513 private void checkHasResponse() throws IOException {
514 if (!mHasResponse) throw new IllegalStateException("No response.");
mef 2015/04/06 16:14:55 isn't it a duplicate of mResponseInfo == null?
xunjieli 2015/04/06 18:09:30 Nope. mResponseInfo can be null when the request f
402 if (mException != null) { 515 if (mException != null) {
403 throw mException; 516 throw mException;
404 } else if (mResponseInfo == null) { 517 } else if (mResponseInfo == null) {
405 throw new NullPointerException( 518 throw new NullPointerException(
406 "Response info is null when there is no exception."); 519 "Response info is null when there is no exception.");
407 } 520 }
408 } 521 }
409 522
410 /** 523 /**
411 * Helper method to return the response header field at position pos. 524 * Helper method to return the response header field at position pos.
412 */ 525 */
413 private Pair<String, String> getHeaderFieldPair(int pos) { 526 private Pair<String, String> getHeaderFieldPair(int pos) {
414 try { 527 try {
415 connect(); 528 getResponse();
416 } catch (IOException e) { 529 } catch (IOException e) {
417 return null; 530 return null;
418 } 531 }
419 List<Pair<String, String>> headers = 532 List<Pair<String, String>> headers =
420 mResponseInfo.getAllHeadersAsList(); 533 mResponseInfo.getAllHeadersAsList();
421 if (pos >= headers.size()) { 534 if (pos >= headers.size()) {
422 return null; 535 return null;
423 } 536 }
424 return headers.get(pos); 537 return headers.get(pos);
425 } 538 }
426 } 539 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698