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 = new Object(); | |
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 mUrlRequestAdapter = nativeCreateRequestAdapter( | |
96 mRequestContext.getChromiumUrlRequestContextAdapter(), | |
97 mUrl, | |
98 mPriority); | |
48 mListener = listener; | 99 mListener = listener; |
49 } | 100 } |
50 | 101 |
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 | 102 @Override |
69 public void setOffset(long offset) { | 103 public void setOffset(long offset) { |
70 mOffset = offset; | 104 mOffset = offset; |
71 if (offset != 0) { | 105 if (offset != 0) { |
72 addHeader("Range", "bytes=" + offset + "-"); | 106 addHeader("Range", "bytes=" + offset + "-"); |
73 } | 107 } |
74 } | 108 } |
75 | 109 |
110 /** | |
111 * The compressed content length as reported by the server. May be -1 if | |
112 * the server did not provide a length. Some servers may also report the | |
113 * wrong number. Since this is the compressed content length, and only | |
114 * uncompressed content is returned by the consumer, the consumer should | |
115 * not rely on this value. | |
116 */ | |
76 @Override | 117 @Override |
77 public long getContentLength() { | 118 public long getContentLength() { |
78 return mContentLength; | 119 return mContentLength; |
79 } | 120 } |
80 | 121 |
81 @Override | 122 @Override |
82 public void setContentLengthLimit(long limit, boolean cancelEarly) { | 123 public void setContentLengthLimit(long limit, boolean cancelEarly) { |
83 mContentLengthLimit = limit; | 124 mContentLengthLimit = limit; |
84 mCancelIfContentLengthOverLimit = cancelEarly; | 125 mCancelIfContentLengthOverLimit = cancelEarly; |
85 } | 126 } |
86 | 127 |
87 @Override | 128 @Override |
88 protected void onResponseStarted() { | 129 public int getHttpStatusCode() { |
89 super.onResponseStarted(); | 130 int httpStatusCode = nativeGetHttpStatusCode(mUrlRequestAdapter); |
90 | 131 |
91 mContentLength = super.getContentLength(); | 132 // TODO(mef): Investigate the following: |
133 // If we have been able to successfully resume a previously interrupted | |
134 // download, the status code will be 206, not 200. Since the rest of the | |
135 // application is 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. Must be done before request has started. | |
194 */ | |
195 public void addHeader(String header, String value) { | |
196 synchronized (mLock) { | |
197 validateNotStarted(); | |
198 if (mAdditionalHeaders == null) { | |
199 mAdditionalHeaders = new HashMap<String, String>(); | |
200 } | |
201 mAdditionalHeaders.put(header, value); | |
202 } | |
203 } | |
204 | |
205 /** | |
206 * Sets data to upload as part of a POST or PUT request. | |
207 * | |
208 * @param contentType MIME type of the upload content or null if this is not | |
209 * an upload. | |
210 * @param data The content that needs to be uploaded. | |
211 */ | |
212 public void setUploadData(String contentType, byte[] data) { | |
213 synchronized (mLock) { | |
214 validateNotStarted(); | |
215 mUploadContentType = contentType; | |
216 mUploadData = data; | |
217 mUploadChannel = null; | |
218 } | |
219 } | |
220 | |
221 /** | |
222 * Sets a readable byte channel to upload as part of a POST or PUT request. | |
223 * | |
224 * @param contentType MIME type of the upload content or null if this is not | |
225 * an upload request. | |
226 * @param channel The channel to read to read upload data from if this is an | |
227 * upload request. | |
228 * @param contentLength The length of data to upload. | |
229 */ | |
230 public void setUploadChannel(String contentType, | |
231 ReadableByteChannel channel, long contentLength) { | |
232 synchronized (mLock) { | |
233 validateNotStarted(); | |
234 mUploadContentType = contentType; | |
235 mUploadChannel = channel; | |
236 mUploadContentLength = contentLength; | |
237 mUploadData = null; | |
238 } | |
239 } | |
240 | |
241 /** | |
242 * Sets HTTP method for upload request. Only PUT or POST are allowed. | |
243 */ | |
244 public void setHttpMethod(String method) { | |
245 validateNotStarted(); | |
246 if (!("PUT".equals(method) || "POST".equals(method))) { | |
247 throw new IllegalArgumentException("Only PUT or POST are allowed."); | |
248 } | |
249 mMethod = method; | |
250 } | |
251 | |
252 public WritableByteChannel getSink() { | |
253 return mSink; | |
254 } | |
255 | |
256 public void start() { | |
257 synchronized (mLock) { | |
258 if (mCanceled) { | |
259 return; | |
260 } | |
261 | |
262 validateNotStarted(); | |
263 validateNotRecycled(); | |
264 | |
265 mStarted = true; | |
266 | |
267 String method = mMethod; | |
268 if (method == null && | |
269 ((mUploadData != null && mUploadData.length > 0) || | |
270 mUploadChannel != null)) { | |
271 // Default to POST if there is data to upload but no method was | |
272 // specified. | |
273 method = "POST"; | |
274 } | |
275 | |
276 if (method != null) { | |
277 nativeSetMethod(mUrlRequestAdapter, method); | |
278 } | |
279 | |
280 if (mHeaders != null && !mHeaders.isEmpty()) { | |
281 for (Entry<String, String> entry : mHeaders.entrySet()) { | |
282 nativeAddHeader(mUrlRequestAdapter, entry.getKey(), | |
283 entry.getValue()); | |
284 } | |
285 } | |
286 | |
287 if (mAdditionalHeaders != null) { | |
288 for (Entry<String, String> entry : | |
289 mAdditionalHeaders.entrySet()) { | |
290 nativeAddHeader(mUrlRequestAdapter, entry.getKey(), | |
291 entry.getValue()); | |
292 } | |
293 } | |
294 | |
295 if (mUploadData != null && mUploadData.length > 0) { | |
296 nativeSetUploadData(mUrlRequestAdapter, mUploadContentType, | |
297 mUploadData); | |
298 } else if (mUploadChannel != null) { | |
299 nativeSetUploadChannel(mUrlRequestAdapter, mUploadContentType, | |
300 mUploadContentLength); | |
301 } | |
302 | |
303 nativeStart(mUrlRequestAdapter); | |
304 } | |
305 } | |
306 | |
307 public void cancel() { | |
308 synchronized (mLock) { | |
309 if (mCanceled) { | |
310 return; | |
311 } | |
312 | |
313 mCanceled = true; | |
314 | |
315 if (!mRecycled) { | |
316 nativeCancel(mUrlRequestAdapter); | |
317 } | |
318 } | |
319 } | |
320 | |
321 public boolean isCanceled() { | |
322 synchronized (mLock) { | |
323 return mCanceled; | |
324 } | |
325 } | |
326 | |
327 public boolean isRecycled() { | |
328 synchronized (mLock) { | |
329 return mRecycled; | |
330 } | |
331 } | |
332 | |
333 public String getContentType() { | |
334 return mContentType; | |
335 } | |
336 | |
337 public String getHeader(String name) { | |
338 validateHeadersAvailable(); | |
339 return nativeGetHeader(mUrlRequestAdapter, name); | |
340 } | |
341 | |
342 // All response headers. | |
343 public Map<String, List<String>> getAllHeaders() { | |
344 validateHeadersAvailable(); | |
345 ResponseHeadersMap result = new ResponseHeadersMap(); | |
346 nativeGetAllHeaders(mUrlRequestAdapter, result); | |
347 return result; | |
348 } | |
349 | |
350 public String getUrl() { | |
351 return mUrl; | |
352 } | |
353 | |
354 private static int convertRequestPriority(int priority) { | |
355 switch (priority) { | |
356 case HttpUrlRequest.REQUEST_PRIORITY_IDLE: | |
357 return ChromiumUrlRequestPriority.IDLE; | |
358 case HttpUrlRequest.REQUEST_PRIORITY_LOWEST: | |
359 return ChromiumUrlRequestPriority.LOWEST; | |
360 case HttpUrlRequest.REQUEST_PRIORITY_LOW: | |
361 return ChromiumUrlRequestPriority.LOW; | |
362 case HttpUrlRequest.REQUEST_PRIORITY_MEDIUM: | |
363 return ChromiumUrlRequestPriority.MEDIUM; | |
364 case HttpUrlRequest.REQUEST_PRIORITY_HIGHEST: | |
365 return ChromiumUrlRequestPriority.HIGHEST; | |
366 default: | |
367 return ChromiumUrlRequestPriority.MEDIUM; | |
368 } | |
369 } | |
370 | |
371 private void onContentLengthOverLimit() { | |
372 mContentLengthOverLimit = true; | |
373 cancel(); | |
374 } | |
375 | |
376 /** | |
377 * A callback invoked when the response has been fully consumed. | |
378 */ | |
379 private void onRequestComplete() { | |
380 mListener.onRequestComplete(this); | |
381 } | |
382 | |
383 private void validateNotRecycled() { | |
384 if (mRecycled) { | |
385 throw new IllegalStateException("Accessing recycled request"); | |
386 } | |
387 } | |
388 | |
389 private void validateNotStarted() { | |
390 if (mStarted) { | |
391 throw new IllegalStateException("Request already started"); | |
392 } | |
393 } | |
394 | |
395 private void validateHeadersAvailable() { | |
396 if (!mHeadersAvailable) { | |
397 throw new IllegalStateException("Response headers not available"); | |
398 } | |
399 } | |
400 | |
401 // Private methods called by native library. | |
402 | |
403 /** | |
404 * A callback invoked when the first chunk of the response has arrived. | |
405 */ | |
406 @CalledByNative | |
407 private void onResponseStarted() { | |
408 mContentType = nativeGetContentType(mUrlRequestAdapter); | |
409 mContentLength = nativeGetContentLength(mUrlRequestAdapter); | |
410 mHeadersAvailable = true; | |
411 | |
92 if (mContentLengthLimit > 0 && mContentLength > mContentLengthLimit | 412 if (mContentLengthLimit > 0 && mContentLength > mContentLengthLimit |
93 && mCancelIfContentLengthOverLimit) { | 413 && mCancelIfContentLengthOverLimit) { |
94 onContentLengthOverLimit(); | 414 onContentLengthOverLimit(); |
95 return; | 415 return; |
96 } | 416 } |
97 | 417 |
98 if (mBufferFullResponse && mContentLength != -1 | 418 if (mBufferFullResponse && mContentLength != -1 |
99 && !mContentLengthOverLimit) { | 419 && !mContentLengthOverLimit) { |
100 ((ChunkedWritableByteChannel)getSink()).setCapacity( | 420 ((ChunkedWritableByteChannel)getSink()).setCapacity( |
101 (int)mContentLength); | 421 (int)mContentLength); |
102 } | 422 } |
103 | 423 |
104 if (mOffset != 0) { | 424 if (mOffset != 0) { |
105 // The server may ignore the request for a byte range. | 425 // The server may ignore the request for a byte range, in which case |
106 if (super.getHttpStatusCode() == 200) { | 426 // status code will be 200, instead of 206. Note that we cannot call |
427 // getHttpStatusCode as it rewrites 206 into 200. | |
428 if (nativeGetHttpStatusCode(mUrlRequestAdapter) == 200) { | |
mef
2014/08/14 21:46:22
Using nativeGetHttpStatusCode avoids rewriting in
| |
429 // TODO(mef): Revisit this logic. | |
107 if (mContentLength != -1) { | 430 if (mContentLength != -1) { |
108 mContentLength -= mOffset; | 431 mContentLength -= mOffset; |
109 } | 432 } |
110 mSkippingToOffset = true; | 433 mSkippingToOffset = true; |
111 } else { | 434 } else { |
112 mSize = mOffset; | 435 mSize = mOffset; |
113 } | 436 } |
114 } | 437 } |
115 mListener.onResponseStarted(this); | 438 mListener.onResponseStarted(this); |
116 } | 439 } |
117 | 440 |
118 @Override | 441 /** |
119 protected void onBytesRead(ByteBuffer buffer) { | 442 * Consumes a portion of the response. |
443 * | |
444 * @param byteBuffer The ByteBuffer to append. Must be a direct buffer, and | |
445 * no references to it may be retained after the method ends, as | |
446 * it wraps code managed on the native heap. | |
447 */ | |
448 @CalledByNative | |
449 private void onBytesRead(ByteBuffer buffer) { | |
120 if (mContentLengthOverLimit) { | 450 if (mContentLengthOverLimit) { |
121 return; | 451 return; |
122 } | 452 } |
123 | 453 |
124 int size = buffer.remaining(); | 454 int size = buffer.remaining(); |
125 mSize += size; | 455 mSize += size; |
126 if (mSkippingToOffset) { | 456 if (mSkippingToOffset) { |
127 if (mSize <= mOffset) { | 457 if (mSize <= mOffset) { |
128 return; | 458 return; |
129 } else { | 459 } else { |
130 mSkippingToOffset = false; | 460 mSkippingToOffset = false; |
131 buffer.position((int)(mOffset - (mSize - size))); | 461 buffer.position((int)(mOffset - (mSize - size))); |
132 } | 462 } |
133 } | 463 } |
134 | 464 |
135 if (mContentLengthLimit != 0 && mSize > mContentLengthLimit) { | 465 boolean contentLengthOverLimit = |
466 (mContentLengthLimit != 0 && mSize > mContentLengthLimit); | |
467 if (contentLengthOverLimit) { | |
136 buffer.limit(size - (int)(mSize - mContentLengthLimit)); | 468 buffer.limit(size - (int)(mSize - mContentLengthLimit)); |
137 super.onBytesRead(buffer); | |
138 onContentLengthOverLimit(); | |
139 return; | |
140 } | 469 } |
141 | 470 |
142 super.onBytesRead(buffer); | 471 try { |
472 while (buffer.hasRemaining()) { | |
473 mSink.write(buffer); | |
474 } | |
475 } catch (IOException e) { | |
476 mSinkException = e; | |
477 cancel(); | |
478 } | |
479 if (contentLengthOverLimit) { | |
480 onContentLengthOverLimit(); | |
481 } | |
143 } | 482 } |
144 | 483 |
145 private void onContentLengthOverLimit() { | 484 /** |
146 mContentLengthOverLimit = true; | 485 * Notifies the listener, releases native data structures. |
147 cancel(); | 486 */ |
487 @SuppressWarnings("unused") | |
488 @CalledByNative | |
489 private void finish() { | |
490 synchronized (mLock) { | |
491 mFinished = true; | |
492 | |
493 if (mRecycled) { | |
494 return; | |
495 } | |
496 try { | |
497 mSink.close(); | |
498 } catch (IOException e) { | |
499 // Ignore | |
500 } | |
501 onRequestComplete(); | |
502 nativeDestroyRequestAdapter(mUrlRequestAdapter); | |
503 mUrlRequestAdapter = 0; | |
504 mRecycled = true; | |
505 } | |
148 } | 506 } |
149 | 507 |
150 @Override | 508 /** |
151 protected void onRequestComplete() { | 509 * Appends header |name| with value |value| to |headersMap|. |
152 mListener.onRequestComplete(this); | 510 */ |
511 @SuppressWarnings("unused") | |
512 @CalledByNative | |
513 private void onAppendResponseHeader(ResponseHeadersMap headersMap, | |
514 String name, String value) { | |
515 if (!headersMap.containsKey(name)) { | |
516 headersMap.put(name, new ArrayList<String>()); | |
517 } | |
518 headersMap.get(name).add(value); | |
153 } | 519 } |
154 | 520 |
155 @Override | 521 /** |
156 public int getHttpStatusCode() { | 522 * Reads a sequence of bytes from upload channel into the given buffer. |
157 int httpStatusCode = super.getHttpStatusCode(); | 523 * @param dest The buffer into which bytes are to be transferred. |
158 | 524 * @return Returns number of bytes read (could be 0) or -1 and closes |
159 // TODO(mef): Investigate the following: | 525 * the channel if error occured. |
160 // If we have been able to successfully resume a previously interrupted | 526 */ |
161 // download, | 527 @SuppressWarnings("unused") |
162 // the status code will be 206, not 200. Since the rest of the | 528 @CalledByNative |
163 // application is | 529 private int readFromUploadChannel(ByteBuffer dest) { |
164 // expecting 200 to indicate success, we need to fake it. | 530 if (mUploadChannel == null || !mUploadChannel.isOpen()) |
165 if (httpStatusCode == 206) { | 531 return -1; |
166 httpStatusCode = 200; | 532 try { |
533 int result = mUploadChannel.read(dest); | |
534 if (result < 0) { | |
535 mUploadChannel.close(); | |
536 return 0; | |
537 } | |
538 return result; | |
539 } catch (IOException e) { | |
540 mSinkException = e; | |
541 try { | |
542 mUploadChannel.close(); | |
543 } catch (IOException ignored) { | |
544 // Ignore this exception. | |
545 } | |
546 cancel(); | |
547 return -1; | |
167 } | 548 } |
168 return httpStatusCode; | |
169 } | 549 } |
170 | 550 |
171 @Override | 551 // Native methods are implemented in chromium_url_request.cc. |
172 public IOException getException() { | |
173 IOException ex = super.getException(); | |
174 if (ex == null && mContentLengthOverLimit) { | |
175 ex = new ResponseTooLargeException(); | |
176 } | |
177 return ex; | |
178 } | |
179 | 552 |
180 @Override | 553 private native long nativeCreateRequestAdapter( |
181 public ByteBuffer getByteBuffer() { | 554 long ChromiumUrlRequestContextAdapter, String url, int priority); |
182 return ((ChunkedWritableByteChannel)getSink()).getByteBuffer(); | |
183 } | |
184 | 555 |
185 @Override | 556 private native void nativeAddHeader(long urlRequestAdapter, String name, |
186 public byte[] getResponseAsBytes() { | 557 String value); |
187 return ((ChunkedWritableByteChannel)getSink()).getBytes(); | 558 |
559 private native void nativeSetMethod(long urlRequestAdapter, String method); | |
560 | |
561 private native void nativeSetUploadData(long urlRequestAdapter, | |
562 String contentType, byte[] content); | |
563 | |
564 private native void nativeSetUploadChannel(long urlRequestAdapter, | |
565 String contentType, long contentLength); | |
566 | |
567 private native void nativeStart(long urlRequestAdapter); | |
568 | |
569 private native void nativeCancel(long urlRequestAdapter); | |
570 | |
571 private native void nativeDestroyRequestAdapter(long urlRequestAdapter); | |
572 | |
573 private native int nativeGetErrorCode(long urlRequestAdapter); | |
574 | |
575 private native int nativeGetHttpStatusCode(long urlRequestAdapter); | |
576 | |
577 private native String nativeGetErrorString(long urlRequestAdapter); | |
578 | |
579 private native String nativeGetContentType(long urlRequestAdapter); | |
580 | |
581 private native long nativeGetContentLength(long urlRequestAdapter); | |
582 | |
583 private native String nativeGetHeader(long urlRequestAdapter, String name); | |
584 | |
585 private native void nativeGetAllHeaders(long urlRequestAdapter, | |
586 ResponseHeadersMap headers); | |
587 | |
588 // Explicit class to work around JNI-generator generics confusion. | |
589 private class ResponseHeadersMap extends HashMap<String, List<String>> { | |
188 } | 590 } |
189 } | 591 } |
OLD | NEW |