d1e68952af52600214ca43da0a403d4c18b7e020
[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   public void close() {
201     // do nothing
202   }
203
204   public HttpParams getParams() {
205     return delegate.getParams();
206   }
207
208   public ClientConnectionManager getConnectionManager() {
209     return delegate.getConnectionManager();
210   }
211
212   public HttpResponse execute(HttpUriRequest request) throws IOException {
213     return delegate.execute(request);
214   }
215
216   public HttpResponse execute(HttpUriRequest request, HttpContext context) throws IOException {
217     return delegate.execute(request, context);
218   }
219
220   public HttpResponse execute(HttpHost target, HttpRequest request) throws IOException {
221     return delegate.execute(target, request);
222   }
223
224   public HttpResponse execute(HttpHost target, HttpRequest request,
225                               HttpContext context) throws IOException {
226     return delegate.execute(target, request, context);
227   }
228
229   public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler) throws IOException {
230     return delegate.execute(request, responseHandler);
231   }
232
233   public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context)
234       throws IOException {
235     return delegate.execute(request, responseHandler, context);
236   }
237
238   public <T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler)
239       throws IOException {
240     return delegate.execute(target, request, responseHandler);
241   }
242
243   public <T> T execute(HttpHost target, HttpRequest request,
244                        ResponseHandler<? extends T> responseHandler,
245                        HttpContext context)
246       throws IOException {
247     return delegate.execute(target, request, responseHandler, context);
248   }
249
250   /**
251    * Compress data to send to server.
252    * Creates a Http Entity holding the gzipped data.
253    * The data will not be compressed if it is too short.
254    *
255    * @param data The bytes to compress
256    * @return Entity holding the data
257    */
258   public static AbstractHttpEntity getCompressedEntity(byte[] data) throws IOException {
259     AbstractHttpEntity entity;
260     if (data.length < DEFAULT_SYNC_MIN_GZIP_BYTES) {
261       entity = new ByteArrayEntity(data);
262     } else {
263       ByteArrayOutputStream arr = new ByteArrayOutputStream();
264       OutputStream zipper = new GZIPOutputStream(arr);
265       try {
266         zipper.write(data);
267       } finally {
268         zipper.close();
269       }
270       entity = new ByteArrayEntity(arr.toByteArray());
271       entity.setContentEncoding("gzip");
272     }
273     return entity;
274   }
275
276 }