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

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

Issue 458633002: Merge UrlRequest.java into ChromiumUrlRequest.java (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Address clm's comments. Created 6 years, 4 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 | Annotate | Revision Log
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 package org.chromium.net;
6
7 import org.apache.http.conn.ConnectTimeoutException;
8 import org.chromium.base.CalledByNative;
9 import org.chromium.base.JNINamespace;
10
11 import java.io.IOException;
12 import java.net.MalformedURLException;
13 import java.net.URL;
14 import java.net.UnknownHostException;
15 import java.nio.ByteBuffer;
16 import java.nio.channels.ReadableByteChannel;
17 import java.nio.channels.WritableByteChannel;
18 import java.util.ArrayList;
19 import java.util.HashMap;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Map.Entry;
23
24 /**
25 * Network request using the native http stack implementation.
26 */
27 @JNINamespace("cronet")
28 public class UrlRequest {
29 private static final class ContextLock {
30 }
31
32 private final UrlRequestContext mRequestContext;
33 private final String mUrl;
34 private final int mPriority;
35 private final Map<String, String> mHeaders;
36 private final WritableByteChannel mSink;
37 private Map<String, String> mAdditionalHeaders;
38 private String mUploadContentType;
39 private String mMethod;
40 private byte[] mUploadData;
41 private ReadableByteChannel mUploadChannel;
42 private WritableByteChannel mOutputChannel;
43 private IOException mSinkException;
44 private volatile boolean mStarted;
45 private volatile boolean mCanceled;
46 private volatile boolean mRecycled;
47 private volatile boolean mFinished;
48 private boolean mHeadersAvailable;
49 private String mContentType;
50 private long mContentLength;
51 private long mUploadContentLength;
52 private final ContextLock mLock;
53
54 /**
55 * Native adapter object, owned by UrlRequest.
56 */
57 private long mUrlRequestAdapter;
58
59 /**
60 * Constructor.
61 *
62 * @param requestContext The context.
63 * @param url The URL.
64 * @param priority Request priority, e.g. {@link #REQUEST_PRIORITY_MEDIUM}.
65 * @param headers HTTP headers.
66 * @param sink The output channel into which downloaded content will be
67 * written.
68 */
69 public UrlRequest(UrlRequestContext requestContext, String url,
70 int priority, Map<String, String> headers,
71 WritableByteChannel sink) {
72 if (requestContext == null) {
73 throw new NullPointerException("Context is required");
74 }
75 if (url == null) {
76 throw new NullPointerException("URL is required");
77 }
78 mRequestContext = requestContext;
79 mUrl = url;
80 mPriority = priority;
81 mHeaders = headers;
82 mSink = sink;
83 mLock = new ContextLock();
84 mUrlRequestAdapter = nativeCreateRequestAdapter(
85 mRequestContext.getUrlRequestContextAdapter(), mUrl, mPriority);
86 }
87
88 /**
89 * Adds a request header.
90 */
91 public void addHeader(String header, String value) {
92 validateNotStarted();
93 if (mAdditionalHeaders == null) {
94 mAdditionalHeaders = new HashMap<String, String>();
95 }
96 mAdditionalHeaders.put(header, value);
97 }
98
99 /**
100 * Sets data to upload as part of a POST request.
101 *
102 * @param contentType MIME type of the post content or null if this is not a
103 * POST.
104 * @param data The content that needs to be uploaded if this is a POST
105 * request.
106 */
107 public void setUploadData(String contentType, byte[] data) {
108 synchronized (mLock) {
109 validateNotStarted();
110 mUploadContentType = contentType;
111 mUploadData = data;
112 mUploadChannel = null;
113 }
114 }
115
116 /**
117 * Sets a readable byte channel to upload as part of a POST request.
118 *
119 * @param contentType MIME type of the post content or null if this is not a
120 * POST request.
121 * @param channel The channel to read to read upload data from if this is a
122 * POST request.
123 * @param contentLength The length of data to upload.
124 */
125 public void setUploadChannel(String contentType,
126 ReadableByteChannel channel, long contentLength) {
127 synchronized (mLock) {
128 validateNotStarted();
129 mUploadContentType = contentType;
130 mUploadChannel = channel;
131 mUploadContentLength = contentLength;
132 mUploadData = null;
133 }
134 }
135
136 public void setHttpMethod(String method) {
137 validateNotStarted();
138 if (!("PUT".equals(method) || "POST".equals(method))) {
139 throw new IllegalArgumentException("Only PUT or POST are allowed.");
140 }
141 mMethod = method;
142 }
143
144 public WritableByteChannel getSink() {
145 return mSink;
146 }
147
148 public void start() {
149 synchronized (mLock) {
150 if (mCanceled) {
151 return;
152 }
153
154 validateNotStarted();
155 validateNotRecycled();
156
157 mStarted = true;
158
159 String method = mMethod;
160 if (method == null &&
161 ((mUploadData != null && mUploadData.length > 0) ||
162 mUploadChannel != null)) {
163 // Default to POST if there is data to upload but no method was
164 // specified.
165 method = "POST";
166 }
167
168 if (method != null) {
169 nativeSetMethod(mUrlRequestAdapter, method);
170 }
171
172 if (mHeaders != null && !mHeaders.isEmpty()) {
173 for (Entry<String, String> entry : mHeaders.entrySet()) {
174 nativeAddHeader(mUrlRequestAdapter, entry.getKey(),
175 entry.getValue());
176 }
177 }
178
179 if (mAdditionalHeaders != null) {
180 for (Entry<String, String> entry :
181 mAdditionalHeaders.entrySet()) {
182 nativeAddHeader(mUrlRequestAdapter, entry.getKey(),
183 entry.getValue());
184 }
185 }
186
187 if (mUploadData != null && mUploadData.length > 0) {
188 nativeSetUploadData(mUrlRequestAdapter, mUploadContentType,
189 mUploadData);
190 } else if (mUploadChannel != null) {
191 nativeSetUploadChannel(mUrlRequestAdapter, mUploadContentType,
192 mUploadContentLength);
193 }
194
195 nativeStart(mUrlRequestAdapter);
196 }
197 }
198
199 public void cancel() {
200 synchronized (mLock) {
201 if (mCanceled) {
202 return;
203 }
204
205 mCanceled = true;
206
207 if (!mRecycled) {
208 nativeCancel(mUrlRequestAdapter);
209 }
210 }
211 }
212
213 public boolean isCanceled() {
214 synchronized (mLock) {
215 return mCanceled;
216 }
217 }
218
219 public boolean isRecycled() {
220 synchronized (mLock) {
221 return mRecycled;
222 }
223 }
224
225 /**
226 * Returns an exception if any, or null if the request was completed
227 * successfully.
228 */
229 public IOException getException() {
230 if (mSinkException != null) {
231 return mSinkException;
232 }
233
234 validateNotRecycled();
235
236 int errorCode = nativeGetErrorCode(mUrlRequestAdapter);
237 switch (errorCode) {
238 case UrlRequestError.SUCCESS:
239 return null;
240 case UrlRequestError.UNKNOWN:
241 return new IOException(
242 nativeGetErrorString(mUrlRequestAdapter));
243 case UrlRequestError.MALFORMED_URL:
244 return new MalformedURLException("Malformed URL: " + mUrl);
245 case UrlRequestError.CONNECTION_TIMED_OUT:
246 return new ConnectTimeoutException("Connection timed out");
247 case UrlRequestError.UNKNOWN_HOST:
248 String host;
249 try {
250 host = new URL(mUrl).getHost();
251 } catch (MalformedURLException e) {
252 host = mUrl;
253 }
254 return new UnknownHostException("Unknown host: " + host);
255 default:
256 throw new IllegalStateException(
257 "Unrecognized error code: " + errorCode);
258 }
259 }
260
261 public int getHttpStatusCode() {
262 return nativeGetHttpStatusCode(mUrlRequestAdapter);
263 }
264
265 /**
266 * Content length as reported by the server. May be -1 or incorrect if the
267 * server returns the wrong number, which happens even with Google servers.
268 */
269 public long getContentLength() {
270 return mContentLength;
271 }
272
273 public String getContentType() {
274 return mContentType;
275 }
276
277 public String getHeader(String name) {
278 validateHeadersAvailable();
279 return nativeGetHeader(mUrlRequestAdapter, name);
280 }
281
282 // All response headers.
283 public Map<String, List<String>> getAllHeaders() {
284 validateHeadersAvailable();
285 ResponseHeadersMap result = new ResponseHeadersMap();
286 nativeGetAllHeaders(mUrlRequestAdapter, result);
287 return result;
288 }
289
290 /**
291 * A callback invoked when the first chunk of the response has arrived.
292 */
293 @CalledByNative
294 protected void onResponseStarted() {
295 mContentType = nativeGetContentType(mUrlRequestAdapter);
296 mContentLength = nativeGetContentLength(mUrlRequestAdapter);
297 mHeadersAvailable = true;
298 }
299
300 /**
301 * A callback invoked when the response has been fully consumed.
302 */
303 protected void onRequestComplete() {
304 }
305
306 /**
307 * Consumes a portion of the response.
308 *
309 * @param byteBuffer The ByteBuffer to append. Must be a direct buffer, and
310 * no references to it may be retained after the method ends, as
311 * it wraps code managed on the native heap.
312 */
313 @CalledByNative
314 protected void onBytesRead(ByteBuffer byteBuffer) {
315 try {
316 while (byteBuffer.hasRemaining()) {
317 mSink.write(byteBuffer);
318 }
319 } catch (IOException e) {
320 mSinkException = e;
321 cancel();
322 }
323 }
324
325 /**
326 * Notifies the listener, releases native data structures.
327 */
328 @SuppressWarnings("unused")
329 @CalledByNative
330 private void finish() {
331 synchronized (mLock) {
332 mFinished = true;
333
334 if (mRecycled) {
335 return;
336 }
337 try {
338 mSink.close();
339 } catch (IOException e) {
340 // Ignore
341 }
342 onRequestComplete();
343 nativeDestroyRequestAdapter(mUrlRequestAdapter);
344 mUrlRequestAdapter = 0;
345 mRecycled = true;
346 }
347 }
348
349 /**
350 * Appends header |name| with value |value| to |headersMap|.
351 */
352 @SuppressWarnings("unused")
353 @CalledByNative
354 private void onAppendResponseHeader(ResponseHeadersMap headersMap,
355 String name, String value) {
356 if (!headersMap.containsKey(name)) {
357 headersMap.put(name, new ArrayList<String>());
358 }
359 headersMap.get(name).add(value);
360 }
361
362 /**
363 * Reads a sequence of bytes from upload channel into the given buffer.
364 * @param dest The buffer into which bytes are to be transferred.
365 * @return Returns number of bytes read (could be 0) or -1 and closes
366 * the channel if error occured.
367 */
368 @SuppressWarnings("unused")
369 @CalledByNative
370 private int readFromUploadChannel(ByteBuffer dest) {
371 if (mUploadChannel == null || !mUploadChannel.isOpen())
372 return -1;
373 try {
374 int result = mUploadChannel.read(dest);
375 if (result < 0) {
376 mUploadChannel.close();
377 return 0;
378 }
379 return result;
380 } catch (IOException e) {
381 mSinkException = e;
382 try {
383 mUploadChannel.close();
384 } catch (IOException ignored) {
385 // Ignore this exception.
386 }
387 cancel();
388 return -1;
389 }
390 }
391
392 private void validateNotRecycled() {
393 if (mRecycled) {
394 throw new IllegalStateException("Accessing recycled request");
395 }
396 }
397
398 private void validateNotStarted() {
399 if (mStarted) {
400 throw new IllegalStateException("Request already started");
401 }
402 }
403
404 private void validateHeadersAvailable() {
405 if (!mHeadersAvailable) {
406 throw new IllegalStateException("Response headers not available");
407 }
408 }
409
410 public String getUrl() {
411 return mUrl;
412 }
413
414 private native long nativeCreateRequestAdapter(
415 long urlRequestContextAdapter, String url, int priority);
416
417 private native void nativeAddHeader(long urlRequestAdapter, String name,
418 String value);
419
420 private native void nativeSetMethod(long urlRequestAdapter, String method);
421
422 private native void nativeSetUploadData(long urlRequestAdapter,
423 String contentType, byte[] content);
424
425 private native void nativeSetUploadChannel(long urlRequestAdapter,
426 String contentType, long contentLength);
427
428 private native void nativeStart(long urlRequestAdapter);
429
430 private native void nativeCancel(long urlRequestAdapter);
431
432 private native void nativeDestroyRequestAdapter(long urlRequestAdapter);
433
434 private native int nativeGetErrorCode(long urlRequestAdapter);
435
436 private native int nativeGetHttpStatusCode(long urlRequestAdapter);
437
438 private native String nativeGetErrorString(long urlRequestAdapter);
439
440 private native String nativeGetContentType(long urlRequestAdapter);
441
442 private native long nativeGetContentLength(long urlRequestAdapter);
443
444 private native String nativeGetHeader(long urlRequestAdapter, String name);
445
446 private native void nativeGetAllHeaders(long urlRequestAdapter,
447 ResponseHeadersMap headers);
448
449 // Explicit class to work around JNI-generator generics confusion.
450 private class ResponseHeadersMap extends HashMap<String, List<String>> {
451 }
452 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698