HttpURLConnection.getResponseCode () возвращает -1 при втором вызове - PullRequest
29 голосов
/ 17 сентября 2009

Мне кажется, что в Android 1.5 возникает специфическая проблема, когда используемая библиотека (signpost 1.1-SNAPSHOT) устанавливает два последовательных соединения с удаленным сервером. Второе соединение всегда прерывается с HttpURLConnection.getResponseCode() из -1

Вот тестовый пример, который выявляет проблему:

// BROKEN
public void testDefaultOAuthConsumerAndroidBug() throws Exception {
    for (int i = 0; i < 2; ++i) {
        final HttpURLConnection c = (HttpURLConnection) new URL("https://api.tripit.com/oauth/request_token").openConnection();
        final DefaultOAuthConsumer consumer = new DefaultOAuthConsumer(api_key, api_secret, SignatureMethod.HMAC_SHA1);
        consumer.sign(c);                             // This line...
        final InputStream is = c.getInputStream();
        while( is.read() >= 0 ) ;                     // ... in combination with this line causes responseCode -1 for i==1 when using api.tripit.com but not mail.google.com
        assertTrue(c.getResponseCode() > 0);
    }
}

В принципе, если я подпишу запрос и затем использую весь входной поток, следующий запрос не будет выполнен с кодом результата -1. Похоже, что сбой не произойдет, если я просто прочитаю один символ из входного потока.

Обратите внимание, что это не происходит для любого URL - только конкретные URL, такие как приведенный выше.

Кроме того, если я переключаюсь на использование HttpClient вместо HttpURLConnection, все работает нормально:

// WORKS
public void testCommonsHttpOAuthConsumerAndroidBug() throws Exception {
    for (int i = 0; i < 2; ++i) {
        final HttpGet c = new HttpGet("https://api.tripit.com/oauth/request_token");
        final CommonsHttpOAuthConsumer consumer = new CommonsHttpOAuthConsumer(api_key, api_secret, SignatureMethod.HMAC_SHA1);
        consumer.sign(c);
        final HttpResponse response = new DefaultHttpClient().execute(c);
        final InputStream is = response.getEntity().getContent();
        while( is.read() >= 0 ) ;
        assertTrue( response.getStatusLine().getStatusCode() == 200);
    }
}

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

Есть идеи?

Ответы [ 5 ]

28 голосов
/ 18 сентября 2009

Попробуйте установить это свойство, чтобы увидеть, помогает ли оно,

http.keepAlive=false

Я видел похожие проблемы, когда UrlConnection не понимает ответ сервера и клиент / сервер не синхронизируется.

Если это решит вашу проблему, вы должны получить HTTP-трассировку, чтобы точно узнать, что особенного в ответе.

РЕДАКТИРОВАТЬ: Это изменение только подтверждает мое подозрение. Это не решит твою проблему. Это просто скрывает симптом.

Если ответ на первый запрос равен 200, нам нужна трассировка. Обычно я использую Ethereal / Wireshark для получения трассировки TCP.

Если ваш первый ответ не равен 200, я вижу проблему в вашем коде. С OAuth ответ об ошибке (401) фактически возвращает данные, которые включают ProblemAdvice, Signature Base String и т. Д., Чтобы помочь вам отладить. Вам нужно прочитать все из потока ошибок. В противном случае это может привести к путанице при следующем соединении, и это является причиной -1. В следующем примере показано, как правильно обрабатывать ошибки,

public static String get(String url) throws IOException {

    ByteArrayOutputStream os = new ByteArrayOutputStream();
    URLConnection conn=null;
    byte[] buf = new byte[4096];

    try {
        URL a = new URL(url);
        conn = a.openConnection();
        InputStream is = conn.getInputStream();
        int ret = 0;
        while ((ret = is.read(buf)) > 0) {
            os.write(buf, 0, ret);
        }
        // close the inputstream
        is.close();
        return new String(os.toByteArray());
    } catch (IOException e) {
        try {
            int respCode = ((HttpURLConnection)conn).getResponseCode();
            InputStream es = ((HttpURLConnection)conn).getErrorStream();
            int ret = 0;
            // read the response body
            while ((ret = es.read(buf)) > 0) {
                os.write(buf, 0, ret);
            }
            // close the errorstream
            es.close();
            return "Error response " + respCode + ": " + 
               new String(os.toByteArray());
        } catch(IOException ex) {
            throw ex;
        }
    }
}
10 голосов
/ 27 октября 2009

Я столкнулся с той же проблемой, когда не прочитал все данные из InputStream перед тем, как закрыть его и открыть второе соединение. Это также было исправлено либо с помощью System.setProperty("http.keepAlive", "false");, либо просто зацикливанием, пока я не прочитал остальную часть InputStream.

Не полностью связана с вашей проблемой, но надеюсь, что это поможет кому-то еще с подобной проблемой.

5 голосов
/ 27 марта 2012

Google предоставил элегантный обходной путь, поскольку это происходит только до Froyo:

private void disableConnectionReuseIfNecessary() {
    // HTTP connection reuse which was buggy pre-froyo
    if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
        System.setProperty("http.keepAlive", "false");
    }
}

Cf. http://android -developers.blogspot.ca / 2011/09 / андроиды-HTTP-clients.html

2 голосов
/ 24 апреля 2014

Или вы можете установить HTTP-заголовок в соединении (HttpUrlConnection):

conn.setRequestProperty("Connection", "close");
0 голосов
/ 18 сентября 2009

Можете ли вы проверить, что соединение не закрывается, прежде чем закончите читать ответ? Может быть, HttpClient сразу анализирует код ответа и сохраняет его для будущих запросов, однако HttpURLConnection может возвращать -1 после закрытия соединения?

...