OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / frameworks / base / core / java / android / net / http / AndroidHttpClient.java
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package android.net.http;
18
19 import com.android.internal.http.HttpDateTime;
20 import org.apache.http.Header;
21 import org.apache.http.HttpEntity;
22 import org.apache.http.HttpEntityEnclosingRequest;
23 import org.apache.http.HttpException;
24 import org.apache.http.HttpHost;
25 import org.apache.http.HttpRequest;
26 import org.apache.http.HttpRequestInterceptor;
27 import org.apache.http.HttpResponse;
28 import org.apache.http.entity.AbstractHttpEntity;
29 import org.apache.http.entity.ByteArrayEntity;
30 import org.apache.http.client.HttpClient;
31 import org.apache.http.client.ResponseHandler;
32 import org.apache.http.client.ClientProtocolException;
33 import org.apache.http.client.protocol.ClientContext;
34 import org.apache.http.client.methods.HttpUriRequest;
35 import org.apache.http.client.params.HttpClientParams;
36 import org.apache.http.conn.ClientConnectionManager;
37 import org.apache.http.conn.scheme.PlainSocketFactory;
38 import org.apache.http.conn.scheme.Scheme;
39 import org.apache.http.conn.scheme.SchemeRegistry;
40 import org.apache.http.impl.client.DefaultHttpClient;
41 import org.apache.http.impl.client.RequestWrapper;
42 import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
43 import org.apache.http.params.BasicHttpParams;
44 import org.apache.http.params.HttpConnectionParams;
45 import org.apache.http.params.HttpParams;
46 import org.apache.http.params.HttpProtocolParams;
47 import org.apache.http.protocol.BasicHttpProcessor;
48 import org.apache.http.protocol.HttpContext;
49 import org.apache.http.protocol.BasicHttpContext;
50
51 import java.io.IOException;
52 import java.io.InputStream;
53 import java.io.ByteArrayOutputStream;
54 import java.io.OutputStream;
55 import java.util.zip.GZIPInputStream;
56 import java.util.zip.GZIPOutputStream;
57 import java.net.URI;
58
59 import android.content.Context;
60 import android.content.ContentResolver;
61 import android.net.SSLCertificateSocketFactory;
62 import android.net.SSLSessionCache;
63 import android.os.Looper;
64 import android.util.Log;
65
66 /**
67  * Subclass of the Apache {@link DefaultHttpClient} that is configured with
68  * reasonable default settings and registered schemes for Android, and
69  * also lets the user add {@link HttpRequestInterceptor} classes.
70  * Don't create this directly, use the {@link #newInstance} factory method.
71  *
72  * <p>This client processes cookies but does not retain them by default.
73  * To retain cookies, simply add a cookie store to the HttpContext:</p>
74  *
75  * <pre>context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);</pre>
76  */
77 public final class AndroidHttpClient implements HttpClient {
78
79     // Gzip of data shorter than this probably won't be worthwhile
80     public static long DEFAULT_SYNC_MIN_GZIP_BYTES = 256;
81
82     private static final String TAG = "AndroidHttpClient";
83
84
85     /** Interceptor throws an exception if the executing thread is blocked */
86     private static final HttpRequestInterceptor sThreadCheckInterceptor =
87             new HttpRequestInterceptor() {
88         public void process(HttpRequest request, HttpContext context) {
89             // Prevent the HttpRequest from being sent on the main thread
90             if (Looper.myLooper() != null && Looper.myLooper() == Looper.getMainLooper() ) {
91                 throw new RuntimeException("This thread forbids HTTP requests");
92             }
93         }
94     };
95
96     /**
97      * Create a new HttpClient with reasonable defaults (which you can update).
98      *
99      * @param userAgent to report in your HTTP requests
100      * @param context to use for caching SSL sessions (may be null for no caching)
101      * @return AndroidHttpClient for you to use for all your requests.
102      */
103     public static AndroidHttpClient newInstance(String userAgent, Context context) {
104         HttpParams params = new BasicHttpParams();
105
106         // Turn off stale checking.  Our connections break all the time anyway,
107         // and it's not worth it to pay the penalty of checking every time.
108         HttpConnectionParams.setStaleCheckingEnabled(params, false);
109
110         // Default connection and socket timeout of 20 seconds.  Tweak to taste.
111         HttpConnectionParams.setConnectionTimeout(params, 20 * 1000);
112         HttpConnectionParams.setSoTimeout(params, 20 * 1000);
113         HttpConnectionParams.setSocketBufferSize(params, 8192);
114
115         // Don't handle redirects -- return them to the caller.  Our code
116         // often wants to re-POST after a redirect, which we must do ourselves.
117         HttpClientParams.setRedirecting(params, false);
118
119         // Use a session cache for SSL sockets
120         SSLSessionCache sessionCache = context == null ? null : new SSLSessionCache(context);
121
122         // Set the specified user agent and register standard protocols.
123         HttpProtocolParams.setUserAgent(params, userAgent);
124         SchemeRegistry schemeRegistry = new SchemeRegistry();
125         schemeRegistry.register(new Scheme("http",
126                 PlainSocketFactory.getSocketFactory(), 80));
127         schemeRegistry.register(new Scheme("https",
128                 SSLCertificateSocketFactory.getHttpSocketFactory(30 * 1000, sessionCache), 443));
129
130         ClientConnectionManager manager =
131                 new ThreadSafeClientConnManager(params, schemeRegistry);
132
133         // We use a factory method to modify superclass initialization
134         // parameters without the funny call-a-static-method dance.
135         return new AndroidHttpClient(manager, params);
136     }
137
138     /**
139      * Create a new HttpClient with reasonable defaults (which you can update).
140      * @param userAgent to report in your HTTP requests.
141      * @return AndroidHttpClient for you to use for all your requests.
142      */
143     public static AndroidHttpClient newInstance(String userAgent) {
144         return newInstance(userAgent, null /* session cache */);
145     }
146
147     private final HttpClient delegate;
148
149     private RuntimeException mLeakedException = new IllegalStateException(
150             "AndroidHttpClient created and never closed");
151
152     private AndroidHttpClient(ClientConnectionManager ccm, HttpParams params) {
153         this.delegate = new DefaultHttpClient(ccm, params) {
154             @Override
155             protected BasicHttpProcessor createHttpProcessor() {
156                 // Add interceptor to prevent making requests from main thread.
157                 BasicHttpProcessor processor = super.createHttpProcessor();
158                 processor.addRequestInterceptor(sThreadCheckInterceptor);
159                 processor.addRequestInterceptor(new CurlLogger());
160
161                 return processor;
162             }
163
164             @Override
165             protected HttpContext createHttpContext() {
166                 // Same as DefaultHttpClient.createHttpContext() minus the
167                 // cookie store.
168                 HttpContext context = new BasicHttpContext();
169                 context.setAttribute(
170                         ClientContext.AUTHSCHEME_REGISTRY,
171                         getAuthSchemes());
172                 context.setAttribute(
173                         ClientContext.COOKIESPEC_REGISTRY,
174                         getCookieSpecs());
175                 context.setAttribute(
176                         ClientContext.CREDS_PROVIDER,
177                         getCredentialsProvider());
178                 return context;
179             }
180         };
181     }
182
183     @Override
184     protected void finalize() throws Throwable {
185         super.finalize();
186         if (mLeakedException != null) {
187             Log.e(TAG, "Leak found", mLeakedException);
188             mLeakedException = null;
189         }
190     }
191
192     /**
193      * Modifies a request to indicate to the server that we would like a
194      * gzipped response.  (Uses the "Accept-Encoding" HTTP header.)
195      * @param request the request to modify
196      * @see #getUngzippedContent
197      */
198     public static void modifyRequestToAcceptGzipResponse(HttpRequest request) {
199         request.addHeader("Accept-Encoding", "gzip");
200     }
201
202     /**
203      * Gets the input stream from a response entity.  If the entity is gzipped
204      * then this will get a stream over the uncompressed data.
205      *
206      * @param entity the entity whose content should be read
207      * @return the input stream to read from
208      * @throws IOException
209      */
210     public static InputStream getUngzippedContent(HttpEntity entity)
211             throws IOException {
212         InputStream responseStream = entity.getContent();
213         if (responseStream == null) return responseStream;
214         Header header = entity.getContentEncoding();
215         if (header == null) return responseStream;
216         String contentEncoding = header.getValue();
217         if (contentEncoding == null) return responseStream;
218         if (contentEncoding.contains("gzip")) responseStream
219                 = new GZIPInputStream(responseStream);
220         return responseStream;
221     }
222
223     /**
224      * Release resources associated with this client.  You must call this,
225      * or significant resources (sockets and memory) may be leaked.
226      */
227     public void close() {
228         if (mLeakedException != null) {
229             getConnectionManager().shutdown();
230             mLeakedException = null;
231         }
232     }
233
234     public HttpParams getParams() {
235         return delegate.getParams();
236     }
237
238     public ClientConnectionManager getConnectionManager() {
239         return delegate.getConnectionManager();
240     }
241
242     public HttpResponse execute(HttpUriRequest request) throws IOException {
243         return delegate.execute(request);
244     }
245
246     public HttpResponse execute(HttpUriRequest request, HttpContext context)
247             throws IOException {
248         return delegate.execute(request, context);
249     }
250
251     public HttpResponse execute(HttpHost target, HttpRequest request)
252             throws IOException {
253         return delegate.execute(target, request);
254     }
255
256     public HttpResponse execute(HttpHost target, HttpRequest request,
257             HttpContext context) throws IOException {
258         return delegate.execute(target, request, context);
259     }
260
261     public <T> T execute(HttpUriRequest request, 
262             ResponseHandler<? extends T> responseHandler)
263             throws IOException, ClientProtocolException {
264         return delegate.execute(request, responseHandler);
265     }
266
267     public <T> T execute(HttpUriRequest request,
268             ResponseHandler<? extends T> responseHandler, HttpContext context)
269             throws IOException, ClientProtocolException {
270         return delegate.execute(request, responseHandler, context);
271     }
272
273     public <T> T execute(HttpHost target, HttpRequest request,
274             ResponseHandler<? extends T> responseHandler) throws IOException,
275             ClientProtocolException {
276         return delegate.execute(target, request, responseHandler);
277     }
278
279     public <T> T execute(HttpHost target, HttpRequest request,
280             ResponseHandler<? extends T> responseHandler, HttpContext context)
281             throws IOException, ClientProtocolException {
282         return delegate.execute(target, request, responseHandler, context);
283     }
284
285     /**
286      * Compress data to send to server.
287      * Creates a Http Entity holding the gzipped data.
288      * The data will not be compressed if it is too short.
289      * @param data The bytes to compress
290      * @return Entity holding the data
291      */
292     public static AbstractHttpEntity getCompressedEntity(byte data[], ContentResolver resolver)
293             throws IOException {
294         AbstractHttpEntity entity;
295         if (data.length < getMinGzipSize(resolver)) {
296             entity = new ByteArrayEntity(data);
297         } else {
298             ByteArrayOutputStream arr = new ByteArrayOutputStream();
299             OutputStream zipper = new GZIPOutputStream(arr);
300             zipper.write(data);
301             zipper.close();
302             entity = new ByteArrayEntity(arr.toByteArray());
303             entity.setContentEncoding("gzip");
304         }
305         return entity;
306     }
307
308     /**
309      * Retrieves the minimum size for compressing data.
310      * Shorter data will not be compressed.
311      */
312     public static long getMinGzipSize(ContentResolver resolver) {
313         return DEFAULT_SYNC_MIN_GZIP_BYTES;  // For now, this is just a constant.
314     }
315
316     /* cURL logging support. */
317
318     /**
319      * Logging tag and level.
320      */
321     private static class LoggingConfiguration {
322
323         private final String tag;
324         private final int level;
325
326         private LoggingConfiguration(String tag, int level) {
327             this.tag = tag;
328             this.level = level;
329         }
330
331         /**
332          * Returns true if logging is turned on for this configuration.
333          */
334         private boolean isLoggable() {
335             return Log.isLoggable(tag, level);
336         }
337
338         /**
339          * Prints a message using this configuration.
340          */
341         private void println(String message) {
342             Log.println(level, tag, message);
343         }
344     }
345
346     /** cURL logging configuration. */
347     private volatile LoggingConfiguration curlConfiguration;
348
349     /**
350      * Enables cURL request logging for this client.
351      *
352      * @param name to log messages with
353      * @param level at which to log messages (see {@link android.util.Log})
354      */
355     public void enableCurlLogging(String name, int level) {
356         if (name == null) {
357             throw new NullPointerException("name");
358         }
359         if (level < Log.VERBOSE || level > Log.ASSERT) {
360             throw new IllegalArgumentException("Level is out of range ["
361                 + Log.VERBOSE + ".." + Log.ASSERT + "]");    
362         }
363
364         curlConfiguration = new LoggingConfiguration(name, level);
365     }
366
367     /**
368      * Disables cURL logging for this client.
369      */
370     public void disableCurlLogging() {
371         curlConfiguration = null;
372     }
373
374     /**
375      * Logs cURL commands equivalent to requests.
376      */
377     private class CurlLogger implements HttpRequestInterceptor {
378         public void process(HttpRequest request, HttpContext context)
379                 throws HttpException, IOException {
380             LoggingConfiguration configuration = curlConfiguration;
381             if (configuration != null
382                     && configuration.isLoggable()
383                     && request instanceof HttpUriRequest) {
384                 // Never print auth token -- we used to check ro.secure=0 to
385                 // enable that, but can't do that in unbundled code.
386                 configuration.println(toCurl((HttpUriRequest) request, false));
387             }
388         }
389     }
390
391     /**
392      * Generates a cURL command equivalent to the given request.
393      */
394     private static String toCurl(HttpUriRequest request, boolean logAuthToken) throws IOException {
395         StringBuilder builder = new StringBuilder();
396
397         builder.append("curl ");
398
399         for (Header header: request.getAllHeaders()) {
400             if (!logAuthToken
401                     && (header.getName().equals("Authorization") ||
402                         header.getName().equals("Cookie"))) {
403                 continue;
404             }
405             builder.append("--header \"");
406             builder.append(header.toString().trim());
407             builder.append("\" ");
408         }
409
410         URI uri = request.getURI();
411
412         // If this is a wrapped request, use the URI from the original
413         // request instead. getURI() on the wrapper seems to return a
414         // relative URI. We want an absolute URI.
415         if (request instanceof RequestWrapper) {
416             HttpRequest original = ((RequestWrapper) request).getOriginal();
417             if (original instanceof HttpUriRequest) {
418                 uri = ((HttpUriRequest) original).getURI();
419             }
420         }
421
422         builder.append("\"");
423         builder.append(uri);
424         builder.append("\"");
425
426         if (request instanceof HttpEntityEnclosingRequest) {
427             HttpEntityEnclosingRequest entityRequest =
428                     (HttpEntityEnclosingRequest) request;
429             HttpEntity entity = entityRequest.getEntity();
430             if (entity != null && entity.isRepeatable()) {
431                 if (entity.getContentLength() < 1024) {
432                     ByteArrayOutputStream stream = new ByteArrayOutputStream();
433                     entity.writeTo(stream);
434                     String entityString = stream.toString();
435
436                     // TODO: Check the content type, too.
437                     builder.append(" --data-ascii \"")
438                             .append(entityString)
439                             .append("\"");
440                 } else {
441                     builder.append(" [TOO MUCH DATA TO INCLUDE]");
442                 }
443             }
444         }
445
446         return builder.toString();
447     }
448
449     /**
450      * Returns the date of the given HTTP date string. This method can identify
451      * and parse the date formats emitted by common HTTP servers, such as
452      * <a href="http://www.ietf.org/rfc/rfc0822.txt">RFC 822</a>,
453      * <a href="http://www.ietf.org/rfc/rfc0850.txt">RFC 850</a>,
454      * <a href="http://www.ietf.org/rfc/rfc1036.txt">RFC 1036</a>,
455      * <a href="http://www.ietf.org/rfc/rfc1123.txt">RFC 1123</a> and
456      * <a href="http://www.opengroup.org/onlinepubs/007908799/xsh/asctime.html">ANSI
457      * C's asctime()</a>.
458      *
459      * @return the number of milliseconds since Jan. 1, 1970, midnight GMT.
460      * @throws IllegalArgumentException if {@code dateString} is not a date or
461      *     of an unsupported format.
462      */
463     public static long parseDate(String dateString) {
464         return HttpDateTime.parse(dateString);
465     }
466 }