Android httpclient зависает при втором запросе к серверу (истекло время ожидания соединения) - PullRequest
19 голосов
/ 29 февраля 2012

Я борюсь со следующей проблемой: Мое приложение выполняет последовательность запросов к http-серверу, используя HttpClient.Я использую HttpPut для отправки данных на сервер.Первый запрос идет хорошо и быстро, второй зависает на 40 секунд, а затем я ловлю исключение тайм-аута соединения.Я пытаюсь повторно использовать мой HttpClient и отправить второй запрос через тот же экземпляр.Если я создаю новый HttpClient вместе с новым ConnectionManager, то все работает нормально.

Почему это происходит?И как это исправить и не создавать новый HttpClient каждый раз?

Заранее спасибо.

Вот мой код: (если я комментирую readClient = newHttpClient (readClient) в doPut,возникает проблема.

public class WebTest
{
private HttpClient readClient;
private SchemeRegistry httpreg;
private HttpParams params;

private URI url; //http://my_site.net/data/

protected HttpClient newHttpClient(HttpClient oldClient)
{
    if(oldClient != null)
        oldClient.getConnectionManager().shutdown();

    ClientConnectionManager cm = new SingleClientConnManager(params, httpreg);
    return new DefaultHttpClient(cm, params);
}

protected String doPut(String data)
{
    //****************************
    //Every time we need to send data, we do new connection
    //with new ConnectionManager and close old one
    readClient = newHttpClient(readClient);

    //*****************************


    String responseS = null;
    HttpPut put = new HttpPut(url);
    try
    {
        HttpEntity entity = new StringEntity(data, "UTF-8");
        put.setEntity(entity);
        put.setHeader("Content-Type", "application/json; charset=utf-8");
        put.setHeader("Accept", "application/json");
        put.setHeader("User-Agent", "Apache-HttpClient/WebTest");

        responseS = readClient.execute(put, responseHandler);
    }
    catch(IOException exc)
    {
        //error handling here
    }
    return responseS;
}

public WebTest()
{
    httpreg = new SchemeRegistry();
    Scheme sch = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
    httpreg.register(sch);

    params = new BasicHttpParams();
    ConnPerRoute perRoute = new ConnPerRouteBean(10);
    ConnManagerParams.setMaxConnectionsPerRoute(params, perRoute);
    ConnManagerParams.setMaxTotalConnections(params, 50);
    ConnManagerParams.setTimeout(params, 15000);
    int timeoutConnection = 15000;
    HttpConnectionParams.setConnectionTimeout(params, timeoutConnection);
    // Set the default socket timeout (SO_TIMEOUT) 
    // in milliseconds which is the timeout for waiting for data.
    int timeoutSocket = 40000;
    HttpConnectionParams.setSoTimeout(params, timeoutSocket);
}

private ResponseHandler<String> responseHandler = new ResponseHandler<String>() 
{
    @Override
    public String handleResponse(HttpResponse response)
            throws ClientProtocolException, IOException
    {
        StatusLine statusLine = response.getStatusLine();
        if (statusLine.getStatusCode() >= 300) 
        {
            throw new HttpResponseException(statusLine.getStatusCode(),
                    statusLine.getReasonPhrase());
        }

        HttpEntity entity = response.getEntity();
        if(entity == null)
            return null;

        InputStream instream = entity.getContent();
        return this.toString(entity, instream, "UTF-8");
    }

    public String toString(
            final HttpEntity entity, 
            final InputStream instream, 
            final String defaultCharset) throws IOException, ParseException 
    {
        if (entity == null) 
        {
            throw new IllegalArgumentException("HTTP entity may not be null");
        }

        if (instream == null) 
        {
            return null;
        }
        if (entity.getContentLength() > Integer.MAX_VALUE) 
        {
            throw new IllegalArgumentException("HTTP entity too large to be buffered in memory");
        }
        int i = (int)entity.getContentLength();
        if (i < 0) 
        {
            i = 4096;
        }
        String charset = EntityUtils.getContentCharSet(entity);
        if (charset == null) 
        {
            charset = defaultCharset;
        }
        if (charset == null) 
        {
            charset = HTTP.DEFAULT_CONTENT_CHARSET;
        }

        Reader reader = new InputStreamReader(instream, charset);

        StringBuilder buffer=new StringBuilder(i);
        try 
        {
            char[] tmp = new char[1024];
            int l;
            while((l = reader.read(tmp)) != -1) 
            {
                buffer.append(tmp, 0, l);
            }
        } finally 
        {
            reader.close();
        }

        return buffer.toString();
    }
}; 

}

Ответы [ 7 ]

24 голосов
/ 30 декабря 2012

Звучит так, будто вы не потребляете сущность после того, как завершите обработку ответа. Убедитесь, что вы вставили следующий код в блок finally:

if (httpEntity != null) {
    try {
        httpEntity.consumeContent();
    } catch (IOException e) {
        Log.e(TAG, "", e);
    }
}

Я предлагаю вам прочитать Учебник HttpClient .

13 голосов
/ 29 февраля 2012

Звучит странно, но у меня была точно такая же проблема. Приложение, над которым я работал, делало несколько последовательных запросов на загрузку набора миниатюрных изображений для отображения в ListView, и после второго оно зависало, как будто в коде HttpClient была мертвая блокировка.

Странное исправление, которое я обнаружил, - использовать AndroidHttpClient вместо DefaultHttpClient. Как только я сделал это, и я попробовал много вещей, прежде чем идти по этому пути, он начал работать просто отлично. Просто не забудьте вызвать client.close (), когда закончите с запросом.

AndroidHttpClient описывается в документации как DefaultHttpClient с «разумными настройками по умолчанию и зарегистрированными схемами для Android». Так как это было введено на уровне API 8 (Android 2.2), я откопал источник, чтобы продублировать эти «настройки по умолчанию», чтобы я мог использовать его дальше, чем этот уровень API. Вот мой код для дублирования значений по умолчанию и вспомогательного класса со статическим методом для его безопасного закрытия

public class HttpClientProvider {

    // Default connection and socket timeout of 60 seconds. Tweak to taste.
    private static final int SOCKET_OPERATION_TIMEOUT = 60 * 1000;

    public static DefaultHttpClient newInstance(String userAgent)
    {
        HttpParams params = new BasicHttpParams();

        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        HttpProtocolParams.setContentCharset(params, HTTP.DEFAULT_CONTENT_CHARSET);
        HttpProtocolParams.setUseExpectContinue(params, true);

        HttpConnectionParams.setStaleCheckingEnabled(params, false);
        HttpConnectionParams.setConnectionTimeout(params, SOCKET_OPERATION_TIMEOUT);
        HttpConnectionParams.setSoTimeout(params, SOCKET_OPERATION_TIMEOUT);
        HttpConnectionParams.setSocketBufferSize(params, 8192);

        SchemeRegistry schReg = new SchemeRegistry();
        schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        schReg.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
        ClientConnectionManager conMgr = new ThreadSafeClientConnManager(params, schReg);

        DefaultHttpClient client = new DefaultHttpClient(conMgr, params);

        return client;
    }

}

А в другом классе ...

public static void safeClose(HttpClient client)
{
    if(client != null && client.getConnectionManager() != null)
    {
        client.getConnectionManager().shutdown();
    }
}
6 голосов
/ 30 марта 2012

У меня такая же проблема при выполнении нескольких запросов в цикле.

Вы можете решить эту проблему, прочитав all из response.getEntity().

3 голосов
/ 05 марта 2013

Я думал, что уточню другие ответы. Я тоже столкнулся с этой проблемой. Проблема была в том, что я не потреблял контент.

Похоже, что если вы этого не сделаете, соединение будет удерживаться, и вы не сможете отправить новый запрос с тем же соединением. Для меня это была особенно трудная ошибка, которую я обнаружил, поскольку я использовал BasicResponseHandler , предоставляемый в Android. Код выглядит так ...

public String handleResponse(final HttpResponse response)
            throws HttpResponseException, IOException {
        StatusLine statusLine = response.getStatusLine();
        if (statusLine.getStatusCode() >= 300) {
            throw new HttpResponseException(statusLine.getStatusCode(),
                    statusLine.getReasonPhrase());
        }

        HttpEntity entity = response.getEntity();
       return entity == null ? null : EntityUtils.toString(entity);
    }

Так что, если строка состояния выше 300, то я не использую контент. И в моем случае был контент. Я сделал свой собственный класс, как это ...

public class StringHandler implements ResponseHandler<String>{

    @Override
    public BufferedInputStream handleResponse(HttpResponse response) throws IOException {
    public String handleResponse(final HttpResponse response)
                throws HttpResponseException, IOException {
            StatusLine statusLine = response.getStatusLine();
           HttpEntity entity = response.getEntity();
            if (statusLine.getStatusCode() >= 300) {
                if (entity != null) {
                    entity.consumeContent();
                }
                throw new HttpResponseException(statusLine.getStatusCode(),
                        statusLine.getReasonPhrase());
            }


           return entity == null ? null : EntityUtils.toString(entity);
        }
    }

}

Так что в принципе в любом случае потребляйте контент!

1 голос
/ 24 июня 2016

Поскольку многие из этих ответов устарели и зависят от ныне устаревшего метода consumeContent(), я решил ответить альтернативой проблеме Timeout waiting for connection from pool.

    HttpEntity someEntity =  response.getEntity();

    InputStream stream = someEntity.getContent();
    BufferedReader rd = new BufferedReader(new InputStreamReader(stream));

    StringBuffer result = new StringBuffer();
    String line = "";
    while ((line = rd.readLine()) != null) {
        result.append(line);
    }
    // On certain android OS levels / certain hardware, this is not enough.
    stream.close(); // This line is what is recommended in the documentation

Вот чтов документации это показывает:

cz.msebera.android.httpclient.HttpEntity
@java.lang.Deprecated 
public abstract void consumeContent()
                            throws java.io.IOException
This method is deprecated since version 4.1. Please use standard java
convention to ensure resource deallocation by calling
InputStream.close() on the input stream returned by getContent()
1 голос
/ 01 октября 2013

Для решения проблемы достаточно (у меня было то же самое):

EntityUtils.consume(response.getEntity());

Нулевая проверка, выполняемая внутри, потребляет

1 голос
/ 05 января 2013

У меня была такая же проблема. Я потребляю весь контент.

Я обнаружил, что если после выполнения запроса я выполняю сборку мусора, все работает без необходимости закрывать и создавать новый AndroidHttpClient:

System.gc ();

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...