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