eca83cf6398926e99e0e94098779ccbe86268d46
[zxing.git] / android / src / com / google / zxing / client / android / AndroidHttpClient.java
1 /*
2  * Copyright (C) 2008 ZXing authors
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 com.google.zxing.client.android;
18
19 import org.apache.http.Header;
20 import org.apache.http.HttpEntity;
21 import org.apache.http.HttpHost;
22 import org.apache.http.HttpMessage;
23 import org.apache.http.HttpRequest;
24 import org.apache.http.HttpRequestInterceptor;
25 import org.apache.http.HttpResponse;
26 import org.apache.http.client.HttpClient;
27 import org.apache.http.client.ResponseHandler;
28 import org.apache.http.client.methods.HttpUriRequest;
29 import org.apache.http.client.params.HttpClientParams;
30 import org.apache.http.client.protocol.ClientContext;
31 import org.apache.http.conn.ClientConnectionManager;
32 import org.apache.http.conn.scheme.PlainSocketFactory;
33 import org.apache.http.conn.scheme.Scheme;
34 import org.apache.http.conn.scheme.SchemeRegistry;
35 import org.apache.http.conn.ssl.SSLSocketFactory;
36 import org.apache.http.entity.AbstractHttpEntity;
37 import org.apache.http.entity.ByteArrayEntity;
38 import org.apache.http.impl.client.DefaultHttpClient;
39 import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
40 import org.apache.http.params.BasicHttpParams;
41 import org.apache.http.params.HttpConnectionParams;
42 import org.apache.http.params.HttpParams;
43 import org.apache.http.params.HttpProtocolParams;
44 import org.apache.http.protocol.BasicHttpContext;
45 import org.apache.http.protocol.BasicHttpProcessor;
46 import org.apache.http.protocol.HttpContext;
47
48 import java.io.ByteArrayOutputStream;
49 import java.io.IOException;
50 import java.io.InputStream;
51 import java.io.OutputStream;
52 import java.util.zip.GZIPInputStream;
53 import java.util.zip.GZIPOutputStream;
54
55 /**
56  * <p>Subclass of the Apache {@link DefaultHttpClient} that is configured with
57  * reasonable default settings and registered schemes for Android, and
58  * also lets the user add {@link HttpRequestInterceptor} classes.
59  * Don't create this directly, use the {@link #newInstance} factory method.</p>
60  * <p/>
61  * <p>This client processes cookies but does not retain them by default.
62  * To retain cookies, simply add a cookie store to the HttpContext:
63  * <pre>context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);</pre>
64  * </p>
65  */
66 public final class AndroidHttpClient implements HttpClient {
67
68   // Gzip of data shorter than this probably won't be worthwhile
69   private static final long DEFAULT_SYNC_MIN_GZIP_BYTES = 256;
70
71   /**
72    * Set if HTTP requests are blocked from being executed on this thread
73    */
74   private static final ThreadLocal<Boolean> sThreadBlocked =
75       new ThreadLocal<Boolean>();
76
77   /**
78    * Interceptor throws an exception if the executing thread is blocked
79    */
80   private static final HttpRequestInterceptor sThreadCheckInterceptor =
81       new HttpRequestInterceptor() {
82         public void process(HttpRequest request, HttpContext context) {
83           if (sThreadBlocked.get() != null && sThreadBlocked.get()) {
84             throw new RuntimeException("This thread forbids HTTP requests");
85           }
86         }
87       };
88
89   /**
90    * Create a new HttpClient with reasonable defaults (which you can update).
91    *
92    * @param userAgent to report in your HTTP requests.
93    * @return AndroidHttpClient for you to use for all your requests.
94    */
95   public static AndroidHttpClient newInstance(String userAgent) {
96     HttpParams params = new BasicHttpParams();
97
98     // Turn off stale checking.  Our connections break all the time anyway,
99     // and it's not worth it to pay the penalty of checking every time.
100     HttpConnectionParams.setStaleCheckingEnabled(params, false);
101
102     // Default connection and socket timeout of 20 seconds.  Tweak to taste.
103     HttpConnectionParams.setConnectionTimeout(params, 20 * 1000);
104     HttpConnectionParams.setSoTimeout(params, 20 * 1000);
105     HttpConnectionParams.setSocketBufferSize(params, 8192);
106
107     // Don't handle redirects -- return them to the caller.  Our code
108     // often wants to re-POST after a redirect, which we must do ourselves.
109     HttpClientParams.setRedirecting(params, false);
110
111     // Set the specified user agent and register standard protocols.
112     HttpProtocolParams.setUserAgent(params, userAgent);
113     SchemeRegistry schemeRegistry = new SchemeRegistry();
114     schemeRegistry.register(new Scheme("http",
115         PlainSocketFactory.getSocketFactory(), 80));
116     schemeRegistry.register(new Scheme("https",
117         SSLSocketFactory.getSocketFactory(), 443));
118     ClientConnectionManager manager =
119         new ThreadSafeClientConnManager(params, schemeRegistry);
120
121     // We use a factory method to modify superclass initialization
122     // parameters without the funny call-a-static-method dance.
123     return new AndroidHttpClient(manager, params);
124   }
125
126   private final HttpClient delegate;
127
128
129   private AndroidHttpClient(ClientConnectionManager ccm, HttpParams params) {
130     this.delegate = new DefaultHttpClient(ccm, params) {
131       @Override
132       protected BasicHttpProcessor createHttpProcessor() {
133         // Add interceptor to prevent making requests from main thread.
134         BasicHttpProcessor processor = super.createHttpProcessor();
135         processor.addRequestInterceptor(sThreadCheckInterceptor);
136         return processor;
137       }
138
139       @Override
140       protected HttpContext createHttpContext() {
141         // Same as DefaultHttpClient.createHttpContext() minus the
142         // cookie store.
143         HttpContext context = new BasicHttpContext();
144         context.setAttribute(ClientContext.AUTHSCHEME_REGISTRY, getAuthSchemes());
145         context.setAttribute(ClientContext.COOKIESPEC_REGISTRY, getCookieSpecs());
146         context.setAttribute(ClientContext.CREDS_PROVIDER, getCredentialsProvider());
147         return context;
148       }
149     };
150   }
151
152   /**
153    * Block this thread from executing HTTP requests.
154    * Used to guard against HTTP requests blocking the main application thread.
155    *
156    * @param blocked if HTTP requests run on this thread should be denied
157    */
158   public static void setThreadBlocked(boolean blocked) {
159     sThreadBlocked.set(blocked);
160   }
161
162   /**
163    * Modifies a request to indicate to the server that we would like a
164    * gzipped response.  (Uses the "Accept-Encoding" HTTP header.)
165    *
166    * @param request the request to modify
167    * @see #getUngzippedContent
168    */
169   public static void modifyRequestToAcceptGzipResponse(HttpMessage request) {
170     request.addHeader("Accept-Encoding", "gzip");
171   }
172
173   /**
174    * Gets the input stream from a response entity.  If the entity is gzipped
175    * then this will get a stream over the uncompressed data.
176    *
177    * @param entity the entity whose content should be read
178    * @return the input stream to read from
179    * @throws IOException
180    */
181   public static InputStream getUngzippedContent(HttpEntity entity) throws IOException {
182     InputStream responseStream = entity.getContent();
183     if (responseStream == null) {
184       return responseStream;
185     }
186     Header header = entity.getContentEncoding();
187     if (header == null) {
188       return responseStream;
189     }
190     String contentEncoding = header.getValue();
191     if (contentEncoding == null) {
192       return responseStream;
193     }
194     if (contentEncoding.contains("gzip")) {
195       responseStream = new GZIPInputStream(responseStream);
196     }
197     return responseStream;
198   }
199
200   /**
201    * Release resources associated with this client.  You must call this,
202    * or significant resources (sockets and memory) may be leaked.
203    */
204   public void close() {
205     getConnectionManager().shutdown();
206   }
207
208   public HttpParams getParams() {
209     return delegate.getParams();
210   }
211
212   public ClientConnectionManager getConnectionManager() {
213     return delegate.getConnectionManager();
214   }
215
216   public HttpResponse execute(HttpUriRequest request) throws IOException {
217     return delegate.execute(request);
218   }
219
220   public HttpResponse execute(HttpUriRequest request, HttpContext context) throws IOException {
221     return delegate.execute(request, context);
222   }
223
224   public HttpResponse execute(HttpHost target, HttpRequest request) throws IOException {
225     return delegate.execute(target, request);
226   }
227
228   public HttpResponse execute(HttpHost target, HttpRequest request,
229                               HttpContext context) throws IOException {
230     return delegate.execute(target, request, context);
231   }
232
233   public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler) throws IOException {
234     return delegate.execute(request, responseHandler);
235   }
236
237   public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context)
238       throws IOException {
239     return delegate.execute(request, responseHandler, context);
240   }
241
242   public <T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler)
243       throws IOException {
244     return delegate.execute(target, request, responseHandler);
245   }
246
247   public <T> T execute(HttpHost target, HttpRequest request,
248                        ResponseHandler<? extends T> responseHandler,
249                        HttpContext context)
250       throws IOException {
251     return delegate.execute(target, request, responseHandler, context);
252   }
253
254   /**
255    * Compress data to send to server.
256    * Creates a Http Entity holding the gzipped data.
257    * The data will not be compressed if it is too short.
258    *
259    * @param data The bytes to compress
260    * @return Entity holding the data
261    */
262   public static AbstractHttpEntity getCompressedEntity(byte[] data) throws IOException {
263     AbstractHttpEntity entity;
264     if (data.length < DEFAULT_SYNC_MIN_GZIP_BYTES) {
265       entity = new ByteArrayEntity(data);
266     } else {
267       ByteArrayOutputStream arr = new ByteArrayOutputStream();
268       OutputStream zipper = new GZIPOutputStream(arr);
269       try {
270         zipper.write(data);
271       } finally {
272         zipper.close();
273       }
274       entity = new ByteArrayEntity(arr.toByteArray());
275       entity.setContentEncoding("gzip");
276     }
277     return entity;
278   }
279
280 }