OLD | NEW |
---|---|
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 // Use a member variable for the long version of fixed content length, since | |
53 // super.fixedContentLengthLong is only added in API 19. | |
54 private long mFixedContentLength = -1; | |
46 | 55 |
47 protected CronetHttpURLConnection(URL url, | 56 public CronetHttpURLConnection(URL url, |
48 UrlRequestContext urlRequestContext) { | 57 UrlRequestContext urlRequestContext) { |
49 super(url); | 58 super(url); |
50 mUrlRequestContext = urlRequestContext; | 59 mUrlRequestContext = urlRequestContext; |
51 mMessageLoop = new MessageLoop(); | 60 mMessageLoop = new MessageLoop(); |
52 mRequest = mUrlRequestContext.createRequest(url.toString(), | 61 mRequest = mUrlRequestContext.createRequest(url.toString(), |
53 new CronetUrlRequestListener(), mMessageLoop); | 62 new CronetUrlRequestListener(), mMessageLoop); |
54 mInputStream = new CronetInputStream(this); | 63 mInputStream = new CronetInputStream(this); |
55 mRequestHeaders = new ArrayList<Pair<String, String>>(); | 64 mRequestHeaders = new ArrayList<Pair<String, String>>(); |
56 } | 65 } |
57 | 66 |
58 /** | 67 /** |
59 * Opens a connection to the resource. If the connect method is called when | 68 * 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 | 69 * 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 | 70 * having the value true), the call is ignored unless an exception is thrown |
62 * previously, in which case, the exception will be rethrown. | 71 * previously, in which case, the exception will be rethrown. |
63 */ | 72 */ |
64 @Override | 73 @Override |
65 public void connect() throws IOException { | 74 public void connect() throws IOException { |
66 if (connected) { | 75 startRequest(true); |
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 } | 76 } |
82 | 77 |
83 /** | 78 /** |
84 * Releases this connection so that its resources may be either reused or | 79 * Releases this connection so that its resources may be either reused or |
85 * closed. | 80 * closed. |
86 */ | 81 */ |
87 @Override | 82 @Override |
88 public void disconnect() { | 83 public void disconnect() { |
89 // Disconnect before connection is made should have no effect. | 84 // Disconnect before connection is made should have no effect. |
90 if (connected) { | 85 if (connected) { |
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
191 throw new IOException("Cannot read response body of a redirect."); | 186 throw new IOException("Cannot read response body of a redirect."); |
192 } | 187 } |
193 // Emulate default implementation's behavior to throw | 188 // Emulate default implementation's behavior to throw |
194 // FileNotFoundException when we get a 400 and above. | 189 // FileNotFoundException when we get a 400 and above. |
195 if (mResponseInfo.getHttpStatusCode() >= HTTP_BAD_REQUEST) { | 190 if (mResponseInfo.getHttpStatusCode() >= HTTP_BAD_REQUEST) { |
196 throw new FileNotFoundException(url.toString()); | 191 throw new FileNotFoundException(url.toString()); |
197 } | 192 } |
198 return mInputStream; | 193 return mInputStream; |
199 } | 194 } |
200 | 195 |
196 @Override | |
197 public OutputStream getOutputStream() throws IOException { | |
198 if (mOutputStream == null) { | |
199 if (connected) { | |
200 throw new ProtocolException( | |
201 "Cannot write to OutputStream after receiving response." ); | |
202 } | |
203 if (mFixedContentLength != -1) { | |
204 addRequestProperty("Content-Length", | |
205 Long.toString(mFixedContentLength)); | |
206 mOutputStream = new CronetOutputStream(this, mFixedContentLength ); | |
207 // Start the request now since all headers can be sent. | |
208 startRequest(false); | |
209 } else { | |
210 // For the buffered case, start the request only when | |
211 // content-length bytes are received, or when an | |
212 // connect action is initiated by the consumer. | |
213 Log.d(TAG, "Outputstream is being buffered in memory."); | |
214 String length = getRequestProperty("Content-Length"); | |
215 if (length == null) { | |
216 mOutputStream = new CronetBufferedOutputStream(this, -1); | |
217 } else { | |
218 long lengthParsed = Long.parseLong(length); | |
219 mOutputStream = new CronetBufferedOutputStream(this, lengthP arsed); | |
220 } | |
221 } | |
222 } | |
223 return mOutputStream; | |
224 } | |
225 | |
226 /** | |
227 * Starts the request if {@code connected} is false. If {@code readResponse} | |
228 * is true, block until the response is received. | |
229 */ | |
230 private void startRequest(boolean readResponse) throws IOException { | |
231 if (connected) { | |
232 if (readResponse) { | |
233 checkHasResponse(); | |
234 } | |
235 return; | |
236 } | |
237 if (mOutputStream != null) { | |
238 // Default Content-Type to application/x-www-form-urlencoded | |
239 if (getRequestProperty("Content-Type") == null) { | |
240 addRequestProperty("Content-Type", | |
241 "application/x-www-form-urlencoded"); | |
242 } | |
243 mRequest.setUploadDataProvider((UploadDataProvider) mOutputStream, | |
244 mMessageLoop); | |
245 } | |
246 connected = true; | |
247 // Start the request. Note that connect() is not used since | |
pauljensen
2015/03/10 17:31:05
Can we move this comment down so it's right before
xunjieli
2015/03/12 21:55:09
Done.
| |
248 // connect() blocks until headers are received. | |
pauljensen
2015/03/10 17:31:05
I think it's a little odd to mention using connect
xunjieli
2015/03/12 21:55:09
Done. Good idea! I was thinking connect() shouldn'
| |
249 for (Pair<String, String> requestHeader : mRequestHeaders) { | |
250 mRequest.addHeader(requestHeader.first, requestHeader.second); | |
251 } | |
252 if (!getUseCaches()) { | |
253 mRequest.disableCache(); | |
254 } | |
255 mRequest.start(); | |
256 if (readResponse) { | |
257 // Blocks until onResponseStarted or onFailed is called. | |
258 mMessageLoop.loop(); | |
259 checkHasResponse(); | |
260 } | |
261 } | |
262 | |
201 /** | 263 /** |
202 * Returns an input stream from the server in the case of an error such as | 264 * 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. | 265 * the requested file has not been found on the remote server. |
204 */ | 266 */ |
205 @Override | 267 @Override |
206 public InputStream getErrorStream() { | 268 public InputStream getErrorStream() { |
207 try { | 269 try { |
208 connect(); | 270 connect(); |
209 } catch (IOException e) { | 271 } catch (IOException e) { |
210 return null; | 272 return null; |
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
294 /** | 356 /** |
295 * Returns whether this connection uses a proxy server. | 357 * Returns whether this connection uses a proxy server. |
296 */ | 358 */ |
297 @Override | 359 @Override |
298 public boolean usingProxy() { | 360 public boolean usingProxy() { |
299 // TODO(xunjieli): implement this. | 361 // TODO(xunjieli): implement this. |
300 return false; | 362 return false; |
301 } | 363 } |
302 | 364 |
303 /** | 365 /** |
366 * Sets chunked streaming mode. | |
367 */ | |
368 @Override | |
369 public void setChunkedStreamingMode(int chunklen) { | |
370 // TODO(xunjieli): implement this. | |
371 throw new UnsupportedOperationException("Chunked mode not supported yet" ); | |
372 } | |
373 | |
374 @Override | |
375 public void setFixedLengthStreamingMode(int contentLength) { | |
376 setFixedLengthStreamingMode((long) contentLength); | |
377 } | |
378 | |
379 @Override | |
pauljensen
2015/03/10 17:31:05
Can we be overriding this if it is not present on
xunjieli
2015/03/12 21:55:09
There's no warning, so I guess it's not prohibited
| |
380 public void setFixedLengthStreamingMode(long contentLength) { | |
381 if (super.connected) { | |
382 throw new IllegalStateException("Already connected"); | |
383 } | |
384 if (super.chunkLength > 0) { | |
385 throw new IllegalStateException("Already in chunked mode"); | |
386 } | |
387 if (contentLength < 0) { | |
388 throw new IllegalArgumentException("contentLength < 0"); | |
389 } | |
390 mFixedContentLength = contentLength; | |
391 super.fixedContentLength = (int) Math.min(contentLength, Integer.MAX_VAL UE); | |
pauljensen
2015/03/10 17:31:05
I still feel like this should call super.setFixedL
xunjieli
2015/03/12 21:55:09
Done. Thanks for the suggestion!
| |
392 } | |
393 | |
394 /** | |
304 * Used by {@link CronetInputStream} to get more data from the network | 395 * Used by {@link CronetInputStream} to get more data from the network |
305 * stack. This should only be called after the request has started. Note | 396 * 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. | 397 * that this call might block if there isn't any more data to be read. |
307 */ | 398 */ |
308 ByteBuffer getMoreData() throws IOException { | 399 ByteBuffer getMoreData() throws IOException { |
309 mResponseByteBuffer = null; | 400 mResponseByteBuffer = null; |
310 mMessageLoop.loop(); | 401 mMessageLoop.loop(); |
311 return mResponseByteBuffer; | 402 return mResponseByteBuffer; |
312 } | 403 } |
313 | 404 |
314 /** | 405 /** |
406 * Used by {@link CronetOutputStream} to wait for data consumed by the | |
407 * network stack before letting the consumer write more data. | |
408 */ | |
409 void waitForPostDataConsumed() throws IOException { | |
410 mMessageLoop.loop(); | |
411 } | |
412 | |
413 /** | |
414 * Used by {@link CronetOutputStream} to wait for consumer to write more dat a. | |
415 */ | |
416 void waitForPostData() { | |
417 mMessageLoop.postQuitTask(); | |
418 } | |
419 | |
420 /** | |
315 * Returns the index of request header in {@link #mRequestHeaders} or | 421 * Returns the index of request header in {@link #mRequestHeaders} or |
316 * -1 if not found. | 422 * -1 if not found. |
317 */ | 423 */ |
318 private int findRequestProperty(String key) { | 424 private int findRequestProperty(String key) { |
319 for (int i = 0; i < mRequestHeaders.size(); i++) { | 425 for (int i = 0; i < mRequestHeaders.size(); i++) { |
320 Pair<String, String> entry = mRequestHeaders.get(i); | 426 Pair<String, String> entry = mRequestHeaders.get(i); |
321 if (entry.first.equalsIgnoreCase(key)) { | 427 if (entry.first.equalsIgnoreCase(key)) { |
322 return i; | 428 return i; |
323 } | 429 } |
324 } | 430 } |
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
417 return null; | 523 return null; |
418 } | 524 } |
419 List<Pair<String, String>> headers = | 525 List<Pair<String, String>> headers = |
420 mResponseInfo.getAllHeadersAsList(); | 526 mResponseInfo.getAllHeadersAsList(); |
421 if (pos >= headers.size()) { | 527 if (pos >= headers.size()) { |
422 return null; | 528 return null; |
423 } | 529 } |
424 return headers.get(pos); | 530 return headers.get(pos); |
425 } | 531 } |
426 } | 532 } |
OLD | NEW |