Подтвердить что ты не робот

HttpURLConnection.getResponseCode() возвращает -1 во втором вызове

Кажется, я сталкиваюсь с особой проблемой на Android 1.5, когда библиотека, которую я использую (signpost 1.1-SNAPSHOT), делает два последовательных подключения к удаленному серверу. Второе соединение всегда терпит неудачу с HttpURLConnection.getResponseCode() of -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);
    }
}

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

Любые идеи?

4b9b3361

Ответ 1

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

http.keepAlive=false

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

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

EDIT: Это изменение просто подтверждает мое подозрение. Это не решает вашу проблему. Он просто скрывает симптом.

Если ответ от первого запроса равен 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;
        }
    }
}

Ответ 2

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

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

Ответ 3

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/androids-http-clients.html

Ответ 4

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

conn.setRequestProperty("Connection", "close");

Ответ 5

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