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; | 5 package org.chromium.net; |
| 6 | 6 |
| 7 import org.apache.http.conn.ConnectTimeoutException; | |
| 8 import org.chromium.base.CalledByNative; | |
| 9 import org.chromium.base.JNINamespace; | |
| 10 | |
| 7 import java.io.IOException; | 11 import java.io.IOException; |
| 12 import java.net.MalformedURLException; | |
| 13 import java.net.URL; | |
| 14 import java.net.UnknownHostException; | |
| 8 import java.nio.ByteBuffer; | 15 import java.nio.ByteBuffer; |
| 16 import java.nio.channels.ReadableByteChannel; | |
| 9 import java.nio.channels.WritableByteChannel; | 17 import java.nio.channels.WritableByteChannel; |
| 18 import java.util.ArrayList; | |
| 19 import java.util.HashMap; | |
| 20 import java.util.List; | |
| 10 import java.util.Map; | 21 import java.util.Map; |
| 22 import java.util.Map.Entry; | |
| 11 | 23 |
| 12 /** | 24 /** |
| 13 * Network request using the native http stack implementation. | 25 * Network request using the native http stack implementation. |
| 14 */ | 26 */ |
| 15 public class ChromiumUrlRequest extends UrlRequest implements HttpUrlRequest { | 27 @JNINamespace("cronet") |
| 28 public class ChromiumUrlRequest implements HttpUrlRequest { | |
| 29 /** | |
| 30 * Native adapter object, owned by UrlRequest. | |
| 31 */ | |
| 32 private long mUrlRequestAdapter; | |
| 33 private final ChromiumUrlRequestContext mRequestContext; | |
| 34 private final String mUrl; | |
| 35 private final int mPriority; | |
| 36 private final Map<String, String> mHeaders; | |
| 37 private final WritableByteChannel mSink; | |
| 38 private Map<String, String> mAdditionalHeaders; | |
| 39 private String mUploadContentType; | |
| 40 private String mMethod; | |
| 41 private byte[] mUploadData; | |
| 42 private ReadableByteChannel mUploadChannel; | |
| 43 private WritableByteChannel mOutputChannel; | |
| 44 private IOException mSinkException; | |
| 45 private volatile boolean mStarted; | |
| 46 private volatile boolean mCanceled; | |
| 47 private volatile boolean mRecycled; | |
| 48 private volatile boolean mFinished; | |
| 49 private boolean mHeadersAvailable; | |
| 50 private String mContentType; | |
| 51 private long mUploadContentLength; | |
| 52 private final HttpUrlRequestListener mListener; | |
| 53 private boolean mBufferFullResponse; | |
| 54 private long mOffset; | |
| 55 private long mContentLength; | |
| 56 private long mContentLengthLimit; | |
| 57 private boolean mCancelIfContentLengthOverLimit; | |
| 58 private boolean mContentLengthOverLimit; | |
| 59 private boolean mSkippingToOffset; | |
| 60 private long mSize; | |
| 61 private final Object mLock; | |
| 16 | 62 |
| 17 private final HttpUrlRequestListener mListener; | 63 public ChromiumUrlRequest(ChromiumUrlRequestContext requestContext, |
| 18 | |
| 19 private boolean mBufferFullResponse; | |
| 20 | |
| 21 private long mOffset; | |
| 22 | |
| 23 private long mContentLength; | |
| 24 | |
| 25 private long mContentLengthLimit; | |
| 26 | |
| 27 private boolean mCancelIfContentLengthOverLimit; | |
| 28 | |
| 29 private boolean mContentLengthOverLimit; | |
| 30 | |
| 31 private boolean mSkippingToOffset; | |
| 32 | |
| 33 private long mSize; | |
| 34 | |
| 35 public ChromiumUrlRequest(UrlRequestContext requestContext, | |
| 36 String url, int priority, Map<String, String> headers, | 64 String url, int priority, Map<String, String> headers, |
| 37 HttpUrlRequestListener listener) { | 65 HttpUrlRequestListener listener) { |
| 38 this(requestContext, url, priority, headers, | 66 this(requestContext, url, priority, headers, |
| 39 new ChunkedWritableByteChannel(), listener); | 67 new ChunkedWritableByteChannel(), listener); |
| 40 mBufferFullResponse = true; | 68 mBufferFullResponse = true; |
| 41 } | 69 } |
| 42 | 70 |
| 43 public ChromiumUrlRequest(UrlRequestContext requestContext, | 71 /** |
| 72 * Constructor. | |
| 73 * | |
| 74 * @param requestContext The context. | |
| 75 * @param url The URL. | |
| 76 * @param priority Request priority, e.g. {@link #REQUEST_PRIORITY_MEDIUM}. | |
| 77 * @param headers HTTP headers. | |
| 78 * @param sink The output channel into which downloaded content will be | |
| 79 * written. | |
| 80 */ | |
| 81 public ChromiumUrlRequest(ChromiumUrlRequestContext requestContext, | |
| 44 String url, int priority, Map<String, String> headers, | 82 String url, int priority, Map<String, String> headers, |
| 45 WritableByteChannel sink, HttpUrlRequestListener listener) { | 83 WritableByteChannel sink, HttpUrlRequestListener listener) { |
| 46 super(requestContext, url, convertRequestPriority(priority), headers, | 84 if (requestContext == null) { |
| 47 sink); | 85 throw new NullPointerException("Context is required"); |
| 86 } | |
| 87 if (url == null) { | |
| 88 throw new NullPointerException("URL is required"); | |
| 89 } | |
| 90 mRequestContext = requestContext; | |
| 91 mUrl = url; | |
| 92 mPriority = convertRequestPriority(priority); | |
| 93 mHeaders = headers; | |
| 94 mSink = sink; | |
| 95 mLock = new Object(); | |
| 96 mUrlRequestAdapter = nativeCreateRequestAdapter( | |
| 97 mRequestContext.getChromiumUrlRequestContextAdapter(), | |
| 98 mUrl, | |
| 99 mPriority); | |
|
mmenke
2014/08/13 15:33:50
Hrm...Not sure about the android style guide, but
mef
2014/08/13 18:52:41
Acknowledged.
| |
| 48 mListener = listener; | 100 mListener = listener; |
| 49 } | 101 } |
| 50 | 102 |
| 51 private static int convertRequestPriority(int priority) { | |
| 52 switch (priority) { | |
| 53 case HttpUrlRequest.REQUEST_PRIORITY_IDLE: | |
| 54 return UrlRequestPriority.IDLE; | |
| 55 case HttpUrlRequest.REQUEST_PRIORITY_LOWEST: | |
| 56 return UrlRequestPriority.LOWEST; | |
| 57 case HttpUrlRequest.REQUEST_PRIORITY_LOW: | |
| 58 return UrlRequestPriority.LOW; | |
| 59 case HttpUrlRequest.REQUEST_PRIORITY_MEDIUM: | |
| 60 return UrlRequestPriority.MEDIUM; | |
| 61 case HttpUrlRequest.REQUEST_PRIORITY_HIGHEST: | |
| 62 return UrlRequestPriority.HIGHEST; | |
| 63 default: | |
| 64 return UrlRequestPriority.MEDIUM; | |
| 65 } | |
| 66 } | |
| 67 | |
| 68 @Override | 103 @Override |
| 69 public void setOffset(long offset) { | 104 public void setOffset(long offset) { |
| 70 mOffset = offset; | 105 mOffset = offset; |
| 71 if (offset != 0) { | 106 if (offset != 0) { |
| 72 addHeader("Range", "bytes=" + offset + "-"); | 107 addHeader("Range", "bytes=" + offset + "-"); |
| 73 } | 108 } |
| 74 } | 109 } |
| 75 | 110 |
| 111 /** | |
| 112 * Content length as reported by the server. May be -1 or incorrect if the | |
| 113 * server returns the wrong number, which happens even with Google servers. | |
|
mmenke
2014/08/13 15:33:50
This doesn't seem quite right. Maybe "The compres
mmenke
2014/08/13 15:33:51
Should we add Javadoc style comments here? @retur
mef
2014/08/13 18:52:41
Done.
mef
2014/08/13 18:52:41
I think so? I think that ideally (or possibly abso
| |
| 114 */ | |
| 76 @Override | 115 @Override |
| 77 public long getContentLength() { | 116 public long getContentLength() { |
| 78 return mContentLength; | 117 return mContentLength; |
| 79 } | 118 } |
| 80 | 119 |
| 81 @Override | 120 @Override |
| 82 public void setContentLengthLimit(long limit, boolean cancelEarly) { | 121 public void setContentLengthLimit(long limit, boolean cancelEarly) { |
| 83 mContentLengthLimit = limit; | 122 mContentLengthLimit = limit; |
| 84 mCancelIfContentLengthOverLimit = cancelEarly; | 123 mCancelIfContentLengthOverLimit = cancelEarly; |
| 85 } | 124 } |
| 86 | 125 |
| 87 @Override | 126 @Override |
| 127 public int getHttpStatusCode() { | |
| 128 int httpStatusCode = nativeGetHttpStatusCode(mUrlRequestAdapter); | |
| 129 | |
| 130 // TODO(mef): Investigate the following: | |
| 131 // If we have been able to successfully resume a previously interrupted | |
| 132 // download, | |
| 133 // the status code will be 206, not 200. Since the rest of the | |
| 134 // application is | |
| 135 // expecting 200 to indicate success, we need to fake it. | |
| 136 if (httpStatusCode == 206) { | |
| 137 httpStatusCode = 200; | |
| 138 } | |
| 139 return httpStatusCode; | |
| 140 } | |
| 141 | |
| 142 /** | |
| 143 * Returns an exception if any, or null if the request was completed | |
| 144 * successfully. | |
| 145 */ | |
| 146 @Override | |
| 147 public IOException getException() { | |
| 148 if (mSinkException != null) { | |
| 149 return mSinkException; | |
| 150 } | |
| 151 | |
| 152 validateNotRecycled(); | |
| 153 | |
| 154 int errorCode = nativeGetErrorCode(mUrlRequestAdapter); | |
| 155 switch (errorCode) { | |
| 156 case ChromiumUrlRequestError.SUCCESS: | |
| 157 if (mContentLengthOverLimit) { | |
| 158 return new ResponseTooLargeException(); | |
| 159 } | |
| 160 return null; | |
| 161 case ChromiumUrlRequestError.UNKNOWN: | |
| 162 return new IOException( | |
| 163 nativeGetErrorString(mUrlRequestAdapter)); | |
| 164 case ChromiumUrlRequestError.MALFORMED_URL: | |
| 165 return new MalformedURLException("Malformed URL: " + mUrl); | |
| 166 case ChromiumUrlRequestError.CONNECTION_TIMED_OUT: | |
| 167 return new ConnectTimeoutException("Connection timed out"); | |
| 168 case ChromiumUrlRequestError.UNKNOWN_HOST: | |
| 169 String host; | |
| 170 try { | |
| 171 host = new URL(mUrl).getHost(); | |
| 172 } catch (MalformedURLException e) { | |
| 173 host = mUrl; | |
| 174 } | |
| 175 return new UnknownHostException("Unknown host: " + host); | |
| 176 default: | |
| 177 throw new IllegalStateException( | |
| 178 "Unrecognized error code: " + errorCode); | |
| 179 } | |
| 180 } | |
| 181 | |
| 182 @Override | |
| 183 public ByteBuffer getByteBuffer() { | |
| 184 return ((ChunkedWritableByteChannel)getSink()).getByteBuffer(); | |
| 185 } | |
| 186 | |
| 187 @Override | |
| 188 public byte[] getResponseAsBytes() { | |
| 189 return ((ChunkedWritableByteChannel)getSink()).getBytes(); | |
| 190 } | |
| 191 | |
| 192 /** | |
| 193 * Adds a request header. | |
| 194 */ | |
| 195 public void addHeader(String header, String value) { | |
|
mmenke
2014/08/13 15:33:50
Should this be synchronized? Heck, should all set
mef
2014/08/13 18:52:41
Acknowledged. I'd prefer to change it in separate
| |
| 196 validateNotStarted(); | |
| 197 if (mAdditionalHeaders == null) { | |
| 198 mAdditionalHeaders = new HashMap<String, String>(); | |
| 199 } | |
| 200 mAdditionalHeaders.put(header, value); | |
| 201 } | |
| 202 | |
| 203 /** | |
| 204 * Sets data to upload as part of a POST request. | |
| 205 * | |
| 206 * @param contentType MIME type of the post content or null if this is not a | |
|
mmenke
2014/08/13 15:33:49
nit: post -> POST
mef
2014/08/13 18:52:41
Done.
| |
| 207 * POST. | |
| 208 * @param data The content that needs to be uploaded if this is a POST | |
| 209 * request. | |
| 210 */ | |
| 211 public void setUploadData(String contentType, byte[] data) { | |
| 212 synchronized (mLock) { | |
| 213 validateNotStarted(); | |
| 214 mUploadContentType = contentType; | |
| 215 mUploadData = data; | |
| 216 mUploadChannel = null; | |
| 217 } | |
| 218 } | |
| 219 | |
| 220 /** | |
| 221 * Sets a readable byte channel to upload as part of a POST request. | |
| 222 * | |
| 223 * @param contentType MIME type of the post content or null if this is not a | |
|
mmenke
2014/08/13 15:33:49
nit POST?
mef
2014/08/13 18:52:41
Done.
| |
| 224 * POST request. | |
| 225 * @param channel The channel to read to read upload data from if this is a | |
| 226 * POST request. | |
| 227 * @param contentLength The length of data to upload. | |
| 228 */ | |
| 229 public void setUploadChannel(String contentType, | |
| 230 ReadableByteChannel channel, long contentLength) { | |
| 231 synchronized (mLock) { | |
| 232 validateNotStarted(); | |
| 233 mUploadContentType = contentType; | |
| 234 mUploadChannel = channel; | |
| 235 mUploadContentLength = contentLength; | |
| 236 mUploadData = null; | |
| 237 } | |
| 238 } | |
| 239 | |
| 240 public void setHttpMethod(String method) { | |
| 241 validateNotStarted(); | |
| 242 if (!("PUT".equals(method) || "POST".equals(method))) { | |
| 243 throw new IllegalArgumentException("Only PUT or POST are allowed."); | |
| 244 } | |
| 245 mMethod = method; | |
| 246 } | |
| 247 | |
| 248 public WritableByteChannel getSink() { | |
| 249 return mSink; | |
| 250 } | |
| 251 | |
| 252 public void start() { | |
| 253 synchronized (mLock) { | |
| 254 if (mCanceled) { | |
| 255 return; | |
| 256 } | |
| 257 | |
| 258 validateNotStarted(); | |
| 259 validateNotRecycled(); | |
| 260 | |
| 261 mStarted = true; | |
| 262 | |
| 263 String method = mMethod; | |
| 264 if (method == null && | |
| 265 ((mUploadData != null && mUploadData.length > 0) || | |
| 266 mUploadChannel != null)) { | |
| 267 // Default to POST if there is data to upload but no method was | |
| 268 // specified. | |
| 269 method = "POST"; | |
| 270 } | |
| 271 | |
| 272 if (method != null) { | |
| 273 nativeSetMethod(mUrlRequestAdapter, method); | |
| 274 } | |
| 275 | |
| 276 if (mHeaders != null && !mHeaders.isEmpty()) { | |
| 277 for (Entry<String, String> entry : mHeaders.entrySet()) { | |
| 278 nativeAddHeader(mUrlRequestAdapter, entry.getKey(), | |
| 279 entry.getValue()); | |
| 280 } | |
|
mmenke
2014/08/13 15:33:51
indent +2
mef
2014/08/13 18:52:41
Done.
| |
| 281 } | |
| 282 | |
| 283 if (mAdditionalHeaders != null) { | |
| 284 for (Entry<String, String> entry : | |
| 285 mAdditionalHeaders.entrySet()) { | |
| 286 nativeAddHeader(mUrlRequestAdapter, entry.getKey(), | |
| 287 entry.getValue()); | |
|
mmenke
2014/08/13 15:33:50
Is is really correct indentation? It looks wrong.
mef
2014/08/13 18:52:41
Yeah, I'm not sure, I think continuation line is i
| |
| 288 } | |
|
mmenke
2014/08/13 15:33:50
indent +2
mef
2014/08/13 18:52:41
Done.
| |
| 289 } | |
| 290 | |
| 291 if (mUploadData != null && mUploadData.length > 0) { | |
| 292 nativeSetUploadData(mUrlRequestAdapter, mUploadContentType, | |
| 293 mUploadData); | |
| 294 } else if (mUploadChannel != null) { | |
| 295 nativeSetUploadChannel(mUrlRequestAdapter, mUploadContentType, | |
| 296 mUploadContentLength); | |
| 297 } | |
| 298 | |
| 299 nativeStart(mUrlRequestAdapter); | |
| 300 } | |
| 301 } | |
| 302 | |
| 303 public void cancel() { | |
| 304 synchronized (mLock) { | |
| 305 if (mCanceled) { | |
| 306 return; | |
| 307 } | |
| 308 | |
| 309 mCanceled = true; | |
| 310 | |
| 311 if (!mRecycled) { | |
| 312 nativeCancel(mUrlRequestAdapter); | |
| 313 } | |
| 314 } | |
| 315 } | |
| 316 | |
| 317 public boolean isCanceled() { | |
| 318 synchronized (mLock) { | |
| 319 return mCanceled; | |
| 320 } | |
| 321 } | |
| 322 | |
| 323 public boolean isRecycled() { | |
| 324 synchronized (mLock) { | |
| 325 return mRecycled; | |
| 326 } | |
| 327 } | |
| 328 | |
| 329 public String getContentType() { | |
| 330 return mContentType; | |
| 331 } | |
| 332 | |
| 333 public String getHeader(String name) { | |
| 334 validateHeadersAvailable(); | |
| 335 return nativeGetHeader(mUrlRequestAdapter, name); | |
| 336 } | |
| 337 | |
| 338 // All response headers. | |
| 339 public Map<String, List<String>> getAllHeaders() { | |
| 340 validateHeadersAvailable(); | |
| 341 ResponseHeadersMap result = new ResponseHeadersMap(); | |
| 342 nativeGetAllHeaders(mUrlRequestAdapter, result); | |
| 343 return result; | |
| 344 } | |
| 345 | |
| 346 public String getUrl() { | |
| 347 return mUrl; | |
| 348 } | |
| 349 | |
| 350 /** | |
| 351 * A callback invoked when the first chunk of the response has arrived. | |
| 352 */ | |
| 353 @CalledByNative | |
| 88 protected void onResponseStarted() { | 354 protected void onResponseStarted() { |
| 89 super.onResponseStarted(); | 355 mContentType = nativeGetContentType(mUrlRequestAdapter); |
| 90 | 356 mContentLength = nativeGetContentLength(mUrlRequestAdapter); |
| 91 mContentLength = super.getContentLength(); | 357 mHeadersAvailable = true; |
| 358 | |
| 92 if (mContentLengthLimit > 0 && mContentLength > mContentLengthLimit | 359 if (mContentLengthLimit > 0 && mContentLength > mContentLengthLimit |
| 93 && mCancelIfContentLengthOverLimit) { | 360 && mCancelIfContentLengthOverLimit) { |
| 94 onContentLengthOverLimit(); | 361 onContentLengthOverLimit(); |
| 95 return; | 362 return; |
| 96 } | 363 } |
| 97 | 364 |
| 98 if (mBufferFullResponse && mContentLength != -1 | 365 if (mBufferFullResponse && mContentLength != -1 |
| 99 && !mContentLengthOverLimit) { | 366 && !mContentLengthOverLimit) { |
| 100 ((ChunkedWritableByteChannel)getSink()).setCapacity( | 367 ((ChunkedWritableByteChannel)getSink()).setCapacity( |
| 101 (int)mContentLength); | 368 (int)mContentLength); |
| 102 } | 369 } |
| 103 | 370 |
| 104 if (mOffset != 0) { | 371 if (mOffset != 0) { |
| 105 // The server may ignore the request for a byte range. | 372 // The server may ignore the request for a byte range. |
| 106 if (super.getHttpStatusCode() == 200) { | 373 if (getHttpStatusCode() == 200) { |
|
Charles
2014/08/14 18:23:50
This breaks range requests. Lower down in the file
mef
2014/08/14 20:58:02
Done.
Charles
2014/08/14 21:33:27
This is still broken - the server will return 20
T
| |
| 107 if (mContentLength != -1) { | 374 if (mContentLength != -1) { |
| 108 mContentLength -= mOffset; | 375 mContentLength -= mOffset; |
|
mmenke
2014/08/13 15:33:50
This just seems bizarre...But fixing it is beyond
mef
2014/08/13 18:52:41
Ack. Added TODO.
| |
| 109 } | 376 } |
| 110 mSkippingToOffset = true; | 377 mSkippingToOffset = true; |
| 111 } else { | 378 } else { |
| 112 mSize = mOffset; | 379 mSize = mOffset; |
| 113 } | 380 } |
| 114 } | 381 } |
| 115 mListener.onResponseStarted(this); | 382 mListener.onResponseStarted(this); |
| 116 } | 383 } |
| 117 | 384 |
| 118 @Override | 385 /** |
| 386 * Consumes a portion of the response. | |
| 387 * | |
| 388 * @param byteBuffer The ByteBuffer to append. Must be a direct buffer, and | |
| 389 * no references to it may be retained after the method ends, as | |
| 390 * it wraps code managed on the native heap. | |
| 391 */ | |
| 392 @CalledByNative | |
| 119 protected void onBytesRead(ByteBuffer buffer) { | 393 protected void onBytesRead(ByteBuffer buffer) { |
| 120 if (mContentLengthOverLimit) { | 394 if (mContentLengthOverLimit) { |
| 121 return; | 395 return; |
| 122 } | 396 } |
| 123 | 397 |
| 124 int size = buffer.remaining(); | 398 int size = buffer.remaining(); |
| 125 mSize += size; | 399 mSize += size; |
| 126 if (mSkippingToOffset) { | 400 if (mSkippingToOffset) { |
| 127 if (mSize <= mOffset) { | 401 if (mSize <= mOffset) { |
| 128 return; | 402 return; |
| 129 } else { | 403 } else { |
| 130 mSkippingToOffset = false; | 404 mSkippingToOffset = false; |
| 131 buffer.position((int)(mOffset - (mSize - size))); | 405 buffer.position((int)(mOffset - (mSize - size))); |
| 132 } | 406 } |
| 133 } | 407 } |
| 134 | 408 |
| 135 if (mContentLengthLimit != 0 && mSize > mContentLengthLimit) { | 409 boolean contentLengthOverLimit = |
| 410 (mContentLengthLimit != 0 && mSize > mContentLengthLimit); | |
| 411 if (contentLengthOverLimit) { | |
| 136 buffer.limit(size - (int)(mSize - mContentLengthLimit)); | 412 buffer.limit(size - (int)(mSize - mContentLengthLimit)); |
| 137 super.onBytesRead(buffer); | |
| 138 onContentLengthOverLimit(); | |
| 139 return; | |
| 140 } | 413 } |
| 141 | 414 |
| 142 super.onBytesRead(buffer); | 415 try { |
| 416 while (buffer.hasRemaining()) { | |
| 417 mSink.write(buffer); | |
| 418 } | |
| 419 } catch (IOException e) { | |
| 420 mSinkException = e; | |
| 421 cancel(); | |
| 422 } | |
| 423 if (contentLengthOverLimit) { | |
| 424 onContentLengthOverLimit(); | |
| 425 } | |
| 426 } | |
| 427 | |
| 428 private static int convertRequestPriority(int priority) { | |
| 429 switch (priority) { | |
| 430 case HttpUrlRequest.REQUEST_PRIORITY_IDLE: | |
| 431 return ChromiumUrlRequestPriority.IDLE; | |
| 432 case HttpUrlRequest.REQUEST_PRIORITY_LOWEST: | |
| 433 return ChromiumUrlRequestPriority.LOWEST; | |
| 434 case HttpUrlRequest.REQUEST_PRIORITY_LOW: | |
| 435 return ChromiumUrlRequestPriority.LOW; | |
| 436 case HttpUrlRequest.REQUEST_PRIORITY_MEDIUM: | |
| 437 return ChromiumUrlRequestPriority.MEDIUM; | |
| 438 case HttpUrlRequest.REQUEST_PRIORITY_HIGHEST: | |
| 439 return ChromiumUrlRequestPriority.HIGHEST; | |
| 440 default: | |
| 441 return ChromiumUrlRequestPriority.MEDIUM; | |
| 442 } | |
| 143 } | 443 } |
| 144 | 444 |
| 145 private void onContentLengthOverLimit() { | 445 private void onContentLengthOverLimit() { |
| 146 mContentLengthOverLimit = true; | 446 mContentLengthOverLimit = true; |
| 147 cancel(); | 447 cancel(); |
| 148 } | 448 } |
| 149 | 449 |
| 150 @Override | 450 /** |
| 451 * A callback invoked when the response has been fully consumed. | |
| 452 */ | |
| 151 protected void onRequestComplete() { | 453 protected void onRequestComplete() { |
| 152 mListener.onRequestComplete(this); | 454 mListener.onRequestComplete(this); |
| 153 } | 455 } |
| 154 | 456 |
| 155 @Override | 457 private void validateNotRecycled() { |
| 156 public int getHttpStatusCode() { | 458 if (mRecycled) { |
| 157 int httpStatusCode = super.getHttpStatusCode(); | 459 throw new IllegalStateException("Accessing recycled request"); |
| 158 | |
| 159 // TODO(mef): Investigate the following: | |
| 160 // If we have been able to successfully resume a previously interrupted | |
| 161 // download, | |
| 162 // the status code will be 206, not 200. Since the rest of the | |
| 163 // application is | |
| 164 // expecting 200 to indicate success, we need to fake it. | |
| 165 if (httpStatusCode == 206) { | |
| 166 httpStatusCode = 200; | |
| 167 } | 460 } |
| 168 return httpStatusCode; | |
| 169 } | 461 } |
| 170 | 462 |
| 171 @Override | 463 private void validateNotStarted() { |
| 172 public IOException getException() { | 464 if (mStarted) { |
| 173 IOException ex = super.getException(); | 465 throw new IllegalStateException("Request already started"); |
| 174 if (ex == null && mContentLengthOverLimit) { | |
| 175 ex = new ResponseTooLargeException(); | |
| 176 } | 466 } |
| 177 return ex; | |
| 178 } | 467 } |
| 179 | 468 |
| 180 @Override | 469 private void validateHeadersAvailable() { |
| 181 public ByteBuffer getByteBuffer() { | 470 if (!mHeadersAvailable) { |
| 182 return ((ChunkedWritableByteChannel)getSink()).getByteBuffer(); | 471 throw new IllegalStateException("Response headers not available"); |
| 472 } | |
| 183 } | 473 } |
| 184 | 474 |
| 185 @Override | 475 /** |
| 186 public byte[] getResponseAsBytes() { | 476 * Notifies the listener, releases native data structures. |
| 187 return ((ChunkedWritableByteChannel)getSink()).getBytes(); | 477 */ |
| 478 @SuppressWarnings("unused") | |
| 479 @CalledByNative | |
| 480 private void finish() { | |
| 481 synchronized (mLock) { | |
| 482 mFinished = true; | |
| 483 | |
| 484 if (mRecycled) { | |
| 485 return; | |
| 486 } | |
| 487 try { | |
| 488 mSink.close(); | |
| 489 } catch (IOException e) { | |
| 490 // Ignore | |
| 491 } | |
| 492 onRequestComplete(); | |
| 493 nativeDestroyRequestAdapter(mUrlRequestAdapter); | |
| 494 mUrlRequestAdapter = 0; | |
| 495 mRecycled = true; | |
| 496 } | |
| 497 } | |
| 498 | |
| 499 /** | |
| 500 * Appends header |name| with value |value| to |headersMap|. | |
| 501 */ | |
| 502 @SuppressWarnings("unused") | |
| 503 @CalledByNative | |
| 504 private void onAppendResponseHeader(ResponseHeadersMap headersMap, | |
| 505 String name, String value) { | |
| 506 if (!headersMap.containsKey(name)) { | |
| 507 headersMap.put(name, new ArrayList<String>()); | |
| 508 } | |
| 509 headersMap.get(name).add(value); | |
| 510 } | |
| 511 | |
| 512 /** | |
| 513 * Reads a sequence of bytes from upload channel into the given buffer. | |
| 514 * @param dest The buffer into which bytes are to be transferred. | |
| 515 * @return Returns number of bytes read (could be 0) or -1 and closes | |
| 516 * the channel if error occured. | |
| 517 */ | |
| 518 @SuppressWarnings("unused") | |
| 519 @CalledByNative | |
| 520 private int readFromUploadChannel(ByteBuffer dest) { | |
| 521 if (mUploadChannel == null || !mUploadChannel.isOpen()) | |
| 522 return -1; | |
| 523 try { | |
| 524 int result = mUploadChannel.read(dest); | |
| 525 if (result < 0) { | |
| 526 mUploadChannel.close(); | |
| 527 return 0; | |
| 528 } | |
| 529 return result; | |
| 530 } catch (IOException e) { | |
| 531 mSinkException = e; | |
| 532 try { | |
| 533 mUploadChannel.close(); | |
| 534 } catch (IOException ignored) { | |
| 535 // Ignore this exception. | |
| 536 } | |
| 537 cancel(); | |
| 538 return -1; | |
| 539 } | |
| 540 } | |
| 541 | |
| 542 // Native methods are implemented in chromium_url_request.cc. | |
| 543 | |
| 544 private native long nativeCreateRequestAdapter( | |
| 545 long ChromiumUrlRequestContextAdapter, String url, int priority); | |
| 546 | |
| 547 private native void nativeAddHeader(long urlRequestAdapter, String name, | |
| 548 String value); | |
| 549 | |
| 550 private native void nativeSetMethod(long urlRequestAdapter, String method); | |
| 551 | |
| 552 private native void nativeSetUploadData(long urlRequestAdapter, | |
| 553 String contentType, byte[] content); | |
| 554 | |
| 555 private native void nativeSetUploadChannel(long urlRequestAdapter, | |
| 556 String contentType, long contentLength); | |
| 557 | |
| 558 private native void nativeStart(long urlRequestAdapter); | |
| 559 | |
| 560 private native void nativeCancel(long urlRequestAdapter); | |
| 561 | |
| 562 private native void nativeDestroyRequestAdapter(long urlRequestAdapter); | |
| 563 | |
| 564 private native int nativeGetErrorCode(long urlRequestAdapter); | |
| 565 | |
| 566 private native int nativeGetHttpStatusCode(long urlRequestAdapter); | |
| 567 | |
| 568 private native String nativeGetErrorString(long urlRequestAdapter); | |
| 569 | |
| 570 private native String nativeGetContentType(long urlRequestAdapter); | |
| 571 | |
| 572 private native long nativeGetContentLength(long urlRequestAdapter); | |
| 573 | |
| 574 private native String nativeGetHeader(long urlRequestAdapter, String name); | |
| 575 | |
| 576 private native void nativeGetAllHeaders(long urlRequestAdapter, | |
| 577 ResponseHeadersMap headers); | |
| 578 | |
| 579 // Explicit class to work around JNI-generator generics confusion. | |
| 580 private class ResponseHeadersMap extends HashMap<String, List<String>> { | |
| 188 } | 581 } |
| 189 } | 582 } |
| OLD | NEW |