I seriously don't understand what is going on...
On Tuesday, November 6, 2012 6:06:33 PM UTC+1, Streets Of Boston wrote:
> Hi Thomas,
> Here is the full-source of my HttpClientUtils.java. Use its static 'get'
> method to obtain an HttpClient. Try to use it and see it it works in your
> case.
> Here is how you could use the HttpClientUtils class (it is thread safe and
> uses pooled connections):
> HttpClient client = null;
> HttpGet request = null;
> HttpResponse response = null;
> try {
> client = *HttpClientUtils.get(context)*;
> request = new HttpGet(url);
> response = client.execute(request);
> someMethodThatParsesTheResponse(response);
> } catch (ClientProtocolException e) {
> // TODO Auto-generated catch block
> } catch (IOException e) {
> // TODO Auto-generated catch block
> }
> finally {
> *HttpClientUtils.release(response, request, client)*;
> }
> Here is the HttpClientUtils source code:
> =========================
> package somepackage;
> import java.io.ByteArrayOutputStream;
> import java.io.IOException;
> import java.io.InputStream;
> import java.io.InterruptedIOException;
> import java.io.OutputStream;
> import java.net.SocketException;
> import java.net.URI;
> import java.net.UnknownHostException;
> import java.util.zip.GZIPInputStream;
> import java.util.zip.GZIPOutputStream;
> import javax.net.ssl.SSLHandshakeException;
> import org.apache.http.Header;
> import org.apache.http.HttpEntity;
> import org.apache.http.HttpEntityEnclosingRequest;
> import org.apache.http.HttpException;
> import org.apache.http.HttpHost;
> import org.apache.http.HttpRequest;
> import org.apache.http.HttpRequestInterceptor;
> import org.apache.http.HttpResponse;
> import org.apache.http.NoHttpResponseException;
> import org.apache.http.client.ClientProtocolException;
> import org.apache.http.client.HttpClient;
> import org.apache.http.client.HttpRequestRetryHandler;
> import org.apache.http.client.ResponseHandler;
> import org.apache.http.client.methods.HttpRequestBase;
> import org.apache.http.client.methods.HttpUriRequest;
> import org.apache.http.client.params.HttpClientParams;
> import org.apache.http.client.protocol.ClientContext;
> import org.apache.http.conn.ClientConnectionManager;
> import org.apache.http.conn.scheme.PlainSocketFactory;
> import org.apache.http.conn.scheme.Scheme;
> import org.apache.http.conn.scheme.SchemeRegistry;
> import org.apache.http.entity.AbstractHttpEntity;
> import org.apache.http.entity.ByteArrayEntity;
> import org.apache.http.impl.client.DefaultHttpClient;
> import org.apache.http.impl.client.RequestWrapper;
> import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
> import org.apache.http.params.BasicHttpParams;
> import org.apache.http.params.HttpConnectionParams;
> import org.apache.http.params.HttpParams;
> import org.apache.http.params.HttpProtocolParams;
> import org.apache.http.protocol.BasicHttpContext;
> import org.apache.http.protocol.BasicHttpProcessor;
> import org.apache.http.protocol.ExecutionContext;
> import org.apache.http.protocol.HttpContext;
> import android.content.ContentResolver;
> import android.content.Context;
> import android.net.SSLCertificateSocketFactory;
> import android.net.SSLSessionCache;
> import android.util.Log;
> /**
> * This class handle HttpClient instances. You can get one and use it to
> release
> * the resources associated with the HttpClient.
> * @author Anton Spaans
> *
> */
> public abstract class HttpClientUtils {
> private static final String USER_AGENT = "Android Blio";
> private static AndroidHttpClient androidHttpClient = null;
> private static final ThreadLocal<ReleaseHolder> RELEASE_HOLDER =
> new ThreadLocal<ReleaseHolder>() {
> @Override
> protected ReleaseHolder initialValue() {
> return new ReleaseHolder();
> }
> };
> /**
> * This method returns a HttpClient that can be used to download info or
> update info
> * on web-services.
> * The HttpClient should be treated as 'read-only': Don't start modifying
> its settings
> * and properties, since this client could be cached and used by multiple
> threads at
> * the same time.
> * @param context.
> * @return An HttpClient
> */
> public static synchronized HttpClient get(Context context) {
> if (androidHttpClient == null) {
> androidHttpClient = AndroidHttpClient.newInstance(USER_AGENT, context);
> }
> return androidHttpClient;
> }
> /**
> * Save a response to be released/closed later.
> * The actual release of it will be done when {@link #releaseNow()} is
> called.
> * @param response
> */
> public static void releaseLater(HttpResponse response) {
> RELEASE_HOLDER.get().response = response;
> }
> /**
> * Save a htttp-request to be released/closed later.
> * The actual release of it will be done when {@link #releaseNow()} is
> called.
> * @param method
> */
> public static void releaseLater(HttpRequestBase method) {
> RELEASE_HOLDER.get().method = method;
> }
> /**
> * Save an HttpClient to be released/closed later.
> * The actual release of it will be done when {@link #releaseNow()} is
> called.
> * @param client
> */
> public static void releaseLater(HttpClient client) {
> RELEASE_HOLDER.get().client = client;
> }
> /**
> * Release any previously saved response, method or client.
> */
> public static void releaseNow() {
> RELEASE_HOLDER.get().cleanUp();
> }
> /**
> * Aborts current request.
> * Call this only on a failure or cancellation.
> */
> public static void abort() {
> HttpRequestBase method = RELEASE_HOLDER.get().method;
> if (method != null) {
> if (!method.isAborted()) {
> method.abort();
> }
> RELEASE_HOLDER.get().method = null;
> }
> }
> /**
> * Release/close the give response, method and client used in the latest
> http-request execution.
> * @param response
> * @param method
> * @param client
> */
> public static void release(HttpResponse response, HttpRequestBase method,
> HttpClient client) {
> if (response != null) {
> HttpEntity entity = response.getEntity();
> if (entity != null) {
> try { entity.consumeContent(); } catch (IOException e) { }
> }
> }
> // if (method != null) {
> // method.abort();
> // }
> // Commented out: The client is being re-used. Don't close it after a
> request.
> // if (client != null && client instanceof AndroidHttpClient) {
> // ((AndroidHttpClient)client).close();
> // }
> }
> private static class ReleaseHolder {
> HttpResponse response;
> HttpRequestBase method;
> HttpClient client;
> public void cleanUp() {
> release(response, method, client);
> response = null;
> method = null;
> client = null;
> }
> }
> /**
> * Retries on failing Http connections. The default handler didn't deal
> with SocketExceptions at
> * all... and those are the ones we get most.
> *
> * @author Anton Spaans
> */
> private static class RetryHandler implements HttpRequestRetryHandler {
> @Override
> public boolean retryRequest(IOException exception, int executionCount,
> HttpContext context) {
> if (exception == null) {
> throw new IllegalArgumentException("Exception parameter may
> not be null");
> }
> if (context == null) {
> throw new IllegalArgumentException("HTTP context may not be
> null");
> }
> if (executionCount > 3) {
> return false;
> }
> if (exception instanceof NoHttpResponseException) {
> // Retry if the server dropped connection on us
> return true;
> }
> if (exception instanceof SocketException) {
> // Retry if the server closed socket on us
> return true;
> }
> if (exception instanceof InterruptedIOException) {
> // Timeout
> return false;
> }
> if (exception instanceof UnknownHostException) {
> // Unknown host
> return false;
> }
> if (exception instanceof SSLHandshakeException) {
> // SSL handshake exception
> return false;
> }
> Boolean b =
> (Boolean)context.getAttribute(ExecutionContext.HTTP_REQ_SENT);
> boolean sent = (b != null && b.booleanValue());
> if (!sent) {
> // Retry if the request has not been sent fully or
> // if it's OK to retry methods that have been sent
> return true;
> }
> HttpRequest request = (HttpRequest)
> context.getAttribute(ExecutionContext.HTTP_REQUEST);
> boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
> if (idempotent) {
> // Retry if the request is considered idempotent
> return true;
> }
> // otherwise do not retry
> return false;
> }
> }
> /**
> * Modified from Google/Android as follows:
> * - When a request happens on the UI-thread, don't throw an exception.
> Just log it.
> * - Added a RetryHandler to handle SocketExceptions.
> *
> *
> * Subclass of the Apache {@link DefaultHttpClient} that is configured with
> * reasonable default settings and registered schemes for Android, and
> * also lets the user add {@link HttpRequestInterceptor} classes.
> * Don't create this directly, use the {@link #newInstance} factory method.
> *
> * <p>This client processes cookies but does not retain them by default.
> * To retain cookies, simply add a cookie store to the HttpContext:</p>
> *
> * <pre>context.setAttribute(ClientContext.COOKIE_STORE,
> cookieStore);</pre>
> */
> public static final class AndroidHttpClient implements HttpClient {
> // Gzip of data shorter than this probably won't be worthwhile
> public static long DEFAULT_SYNC_MIN_GZIP_BYTES = 256;
> // Default connection and socket timeout of 60 seconds. Tweak to
> taste.
> private static final int SOCKET_OPERATION_TIMEOUT = 60 * 1000;
> private static final String TAG = "AndroidHttpClient";
> // Removed ANR warning from logs. Documented in TRAC.
> /** Interceptor throws an exception if the executing thread is blocked
> */
> private static final HttpRequestInterceptor sThreadCheckInterceptor =
> new HttpRequestInterceptor() {
> public void process(HttpRequest request, HttpContext context) {
> // Prevent the HttpRequest from being sent on the main thread
> // if (Looper.myLooper() != null && Looper.myLooper() ==
> Looper.getMainLooper() ) {
> // Log.w("ANR", "!!! Risk for ANR !!!", new Exception("HttpClient is
> being used on the main UI thread."));
> // }
> }
> };
> /**
> * Create a new HttpClient with reasonable defaults (which you can
> update).
> *
> * @param userAgent to report in your HTTP requests
> * @param context to use for caching SSL sessions (may be null for no
> caching)
> * @return AndroidHttpClient for you to use for all your requests.
> */
> public static AndroidHttpClient newInstance(String userAgent, Context
> context) {
> HttpParams params = new BasicHttpParams();
> // Turn off stale checking. Our connections break all the time
> anyway,
> // and it's not worth it to pay the penalty of checking every time.
> HttpConnectionParams.setStaleCheckingEnabled(params, false);
> HttpConnectionParams.setConnectionTimeout(params,
> SOCKET_OPERATION_TIMEOUT);
> HttpConnectionParams.setSoTimeout(params,
> SOCKET_OPERATION_TIMEOUT);
> HttpConnectionParams.setSocketBufferSize(params, 8192);
> // Don't handle redirects -- return them to the caller. Our code
> // often wants to re-POST after a redirect, which we must do
> ourselves.
> HttpClientParams.setRedirecting(params, false);
> // Use a session cache for SSL sockets
> SSLSessionCache sessionCache = context == null ? null : new
> SSLSessionCache(context);
> // Set the specified user agent and register standard protocols.
> HttpProtocolParams.setUserAgent(params, userAgent);
> SchemeRegistry schemeRegistry = new SchemeRegistry();
> schemeRegistry.register(new Scheme("http",
> PlainSocketFactory.getSocketFactory(), 80));
> schemeRegistry.register(new Scheme("https",
> SSLCertificateSocketFactory.getHttpSocketFactory(SOCKET_OPERATION_TIMEOUT,
> sessionCache), 443));
> ClientConnectionManager manager = new
> ThreadSafeClientConnManager(params, schemeRegistry);
> // We use a factory method to modify superclass initialization
> // parameters without the funny call-a-static-method dance.
> return new AndroidHttpClient(manager, params);
> }
> /**
> * Create a new HttpClient with reasonable defaults (which you can
> update).
> * @param userAgent to report in your HTTP requests.
> * @return AndroidHttpClient for you to use for all your requests.
> */
> public static AndroidHttpClient newInstance(String userAgent) {
> return newInstance(userAgent, null /* session cache */);
> }
> private final HttpClient delegate;
> private RuntimeException mLeakedException = new IllegalStateException(
> "AndroidHttpClient created and never closed");
> private final HttpRequestRetryHandler retryHandler = new
> RetryHandler();
> private AndroidHttpClient(ClientConnectionManager ccm, HttpParams
> params) {
> DefaultHttpClient httpClient = new DefaultHttpClient(ccm, params) {
> @Override
> protected BasicHttpProcessor createHttpProcessor() {
> // Add interceptor to prevent making requests from main
> thread.
> BasicHttpProcessor processor = super.createHttpProcessor();
> processor.addRequestInterceptor(sThreadCheckInterceptor);
> processor.addRequestInterceptor(new CurlLogger());
> return processor;
> }
> @Override
> protected HttpContext createHttpContext() {
> // Same as DefaultHttpClient.createHttpContext() minus the
> // cookie store.
> HttpContext context = new BasicHttpContext();
> context.setAttribute(
> ClientContext.AUTHSCHEME_REGISTRY,
> getAuthSchemes());
> context.setAttribute(
> ClientContext.COOKIESPEC_REGISTRY,
> getCookieSpecs());
> context.setAttribute(
> ClientContext.CREDS_PROVIDER,
> getCredentialsProvider());
> return context;
> }
> };
> httpClient.setHttpRequestRetryHandler(retryHandler);
> this.delegate = httpClient;
> }
> @Override
> protected void finalize() throws Throwable {
> super.finalize();
> if (mLeakedException != null) {
> Log.e(TAG, "Leak found", mLeakedException);
> mLeakedException = null;
> }
> }
> /**
> * Modifies a request to indicate to the server that we would like a
> * gzipped response. (Uses the "Accept-Encoding" HTTP header.)
> * @param request the request to modify
> * @see #getUngzippedContent
> */
> public static void modifyRequestToAcceptGzipResponse(HttpRequest
> request) {
> request.addHeader("Accept-Encoding", "gzip");
> }
> /**
> * Gets the input stream from a response entity. If the entity is
> gzipped
> * then this will get a stream over the uncompressed data.
> *
> * @param entity the entity whose content should be read
> * @return the input stream to read from
> * @throws IOException
> */
> public static InputStream getUngzippedContent(HttpEntity entity)
> throws IOException {
> InputStream responseStream = entity.getContent();
> if (responseStream == null) return responseStream;
> Header header = entity.getContentEncoding();
> if (header == null) return responseStream;
> String contentEncoding = header.getValue();
> if (contentEncoding == null) return responseStream;
> if (contentEncoding.contains("gzip")) responseStream
> = new GZIPInputStream(responseStream);
> return responseStream;
> }
> /**
> * Release resources associated with this client. You must call this,
> * or significant resources (sockets and memory) may be leaked.
> */
> public void close() {
> if (mLeakedException != null) {
> getConnectionManager().shutdown();
> mLeakedException = null;
> }
> }
> public HttpParams getParams() {
> return delegate.getParams();
> }
> public ClientConnectionManager getConnectionManager() {
> return delegate.getConnectionManager();
> }
> public HttpResponse execute(HttpUriRequest request) throws IOException
> {
> try {
> return delegate.execute(request);
> } catch (IOException e) {
> try { request.abort(); } catch (Exception ee) {}
> throw e;
> }
> }
> public HttpResponse execute(HttpUriRequest request, HttpContext
> context)
> throws IOException {
> try {
> return delegate.execute(request, context);
> } catch (IOException e) {
> try { request.abort(); } catch (Exception ee) {}
> throw e;
> }
> }
> public HttpResponse execute(HttpHost target, HttpRequest request)
> throws IOException {
> return delegate.execute(target, request);
> }
> public HttpResponse execute(HttpHost target, HttpRequest request,
> HttpContext context) throws IOException {
> return delegate.execute(target, request, context);
> }
> public <T> T execute(HttpUriRequest request,
> ResponseHandler<? extends T> responseHandler)
> throws IOException, ClientProtocolException {
> try {
> return delegate.execute(request, responseHandler);
> } catch (IOException e) {
> try { request.abort(); } catch (Exception ee) {}
> throw e;
> }
> }
> public <T> T execute(HttpUriRequest request,
> ResponseHandler<? extends T> responseHandler, HttpContext
> context)
> throws IOException, ClientProtocolException {
> try {
> return delegate.execute(request, responseHandler, context);
> } catch (IOException e) {
> try { request.abort(); } catch (Exception ee) {}
> throw e;
> }
> }
> public <T> T execute(HttpHost target, HttpRequest request,
> ResponseHandler<? extends T> responseHandler) throws
> IOException,
> ClientProtocolException {
> return delegate.execute(target, request, responseHandler);
> }
> public <T> T execute(HttpHost target, HttpRequest request,
> ResponseHandler<? extends T> responseHandler, HttpContext
> context)
> throws IOException, ClientProtocolException {
> return delegate.execute(target, request, responseHandler, context);
> }
> /**
> * Compress data to send to server.
> * Creates a Http Entity holding the gzipped data.
> * The data will not be compressed if it is too short.
> * @param data The bytes to compress
> * @return Entity holding the data
> */
> public static AbstractHttpEntity getCompressedEntity(byte data[],
> ContentResolver resolver)
> throws IOException {
> AbstractHttpEntity entity;
> if (data.length < getMinGzipSize(resolver)) {
> entity = new ByteArrayEntity(data);
> } else {
> ByteArrayOutputStream arr = new ByteArrayOutputStream();
> OutputStream zipper = new GZIPOutputStream(arr);
> zipper.write(data);
> zipper.close();
> entity = new ByteArrayEntity(arr.toByteArray());
> entity.setContentEncoding("gzip");
> }
> return entity;
> }
> /**
> * Retrieves the minimum size for compressing data.
> * Shorter data will not be compressed.
> */
> public static long getMinGzipSize(ContentResolver resolver) {
> return DEFAULT_SYNC_MIN_GZIP_BYTES; // For now, this is just a
> constant.
> }
> /* cURL logging support. */
> /**
> * Logging tag and level.
> */
> private static class LoggingConfiguration {
> private final String tag;
> private final int level;
> private LoggingConfiguration(String tag, int level) {
> this.tag = tag;
> this.level = level;
> }
> /**
> * Returns true if logging is turned on for this configuration.
> */
> private boolean isLoggable() {
> return Log.isLoggable(tag, level);
> }
> /**
> * Prints a message using this configuration.
> */
> private void println(String message) {
> Log.println(level, tag, message);
> }
> }
> /** cURL logging configuration. */
> private volatile LoggingConfiguration curlConfiguration;
> /**
> * Enables cURL request logging for this client.
> *
> * @param name to log messages with
> * @param level at which to log messages (see {@link android.util.Log})
> */
> public void enableCurlLogging(String name, int level) {
> if (name == null) {
> throw new NullPointerException("name");
> }
> if (level < Log.VERBOSE || level > Log.ASSERT) {
> throw new IllegalArgumentException("Level is out of range ["
> + Log.VERBOSE + ".." + Log.ASSERT + "]");
> }
> curlConfiguration = new LoggingConfiguration(name, level);
> }
> /**
> * Disables cURL logging for this client.
> */
> public void disableCurlLogging() {
> curlConfiguration = null;
> }
> /**
> * Logs cURL commands equivalent to requests.
> */
> private class CurlLogger implements HttpRequestInterceptor {
> public void process(HttpRequest request, HttpContext context)
> throws HttpException, IOException {
> LoggingConfiguration configuration = curlConfiguration;
> if (configuration != null
> && configuration.isLoggable()
> && request instanceof HttpUriRequest) {
> // Never print auth token -- we used to check ro.secure=0
> to
> // enable that, but can't do that in unbundled code.
> configuration.println(toCurl((HttpUriRequest) request,
> false));
> }
> }
> }
> /**
> * Generates a cURL command equivalent to the given request.
> */
> private static String toCurl(HttpUriRequest request, boolean
> logAuthToken) throws IOException {
> StringBuilder builder = new StringBuilder();
> builder.append("curl ");
> for (Header header: request.getAllHeaders()) {
> if (!logAuthToken
> && (header.getName().equals("Authorization") ||
> header.getName().equals("Cookie"))) {
> continue;
> }
> builder.append("--header \"");
> builder.append(header.toString().trim());
> builder.append("\" ");
> }
> URI uri = request.getURI();
> // If this is a wrapped request, use the URI from the original
> // request instead. getURI() on the wrapper seems to return a
> // relative URI. We want an absolute URI.
> if (request instanceof RequestWrapper) {
> HttpRequest original = ((RequestWrapper)
> request).getOriginal();
> if (original instanceof HttpUriRequest) {
> uri = ((HttpUriRequest) original).getURI();
> }
> }
> builder.append("\"");
> builder.append(uri);
> builder.append("\"");
> if (request instanceof HttpEntityEnclosingRequest) {
> HttpEntityEnclosingRequest entityRequest =
> (HttpEntityEnclosingRequest) request;
> HttpEntity entity = entityRequest.getEntity();
> if (entity != null && entity.isRepeatable()) {
> if (entity.getContentLength() < 1024) {
> ByteArrayOutputStream stream = new
> ByteArrayOutputStream();
> entity.writeTo(stream);
> String entityString = stream.toString();
> // TODO: Check the content type, too.
> builder.append(" --data-ascii \"")
> .append(entityString)
> .append("\"");
> } else {
> builder.append(" [TOO MUCH DATA TO INCLUDE]");
> }
> }
> }
> return builder.toString();
> }
> }
> }
> =========================
> On Tuesday, November 6, 2012 11:05:43 AM UTC-5, Thomas Bouron wrote:
>> Ok so I made a few tests and:
>> 1. The solution provided by Street of boston doesn't work, at least
>> fior me. It seems that the RetryHandler is never called and I cannot figure
>> out why
>> 2. I saw nothing unusual with tcpdump, so I think it is not a DNS
>> lookup issue
>> 3. Funny thing, the REST server returns me a JSON response and I
>> discovered that for those with a size less than 8KB, my code works fine!
>> Why? That is the question!
>> On Tuesday, November 6, 2012 4:17:19 PM UTC+1, Thomas Bouron wrote:
>>> Thanks guys, I'll try that keep you updated.
>>> On Tuesday, November 6, 2012 3:58:32 PM UTC+1, Nikolay Elenkov wrote:
>>>> On Tue, Nov 6, 2012 at 11:36 PM, Thomas Bouron <tbo...@gmail.com>
>>>> wrote:
>>>> > Here is my code with AndroidHttpClient:
>>>> ...
>>>> > And my code with HttpURLConnection:
>>>> Looks OK, so most probably it's some network issue. Use tcpdump, etc.
>>>> in you
>>>> work environment to see exactly what is going on, and if it is indeed
>>>> a DNS issue
>>>> (although what you are getting there might be unrelated). Also try to
>>>> find out what
>>>> is common between the users that are having the issue (same area,
>>>> carrier,
>>>> device, Android version, etc.)
>>>> HTH