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

Java.io.IOException: обнаружено не было

Я новичок в android, и это мой первый проект на Android. Я борюсь с проблемой "проверки подлинности" более чем на один день. Я попробовал несколько вариантов, но никто из них не работал.

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

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

Я получаю код состояния 401. Я знаю, это означает, что никакая аутентификация не прошла или не прошла, тогда они ошибаются. Здесь, я уверен, мои прошедшие правильные.

Ниже мой код:

try {
    url = new URL(baseUrl);
}
catch (MalformedURLException me) {
     Log.e(TAG, "URL could not be parsed. URL : " + baseUrl + ". Line : " + getLineNumber(), me);
     me.printStackTrace();
}

try {
    urlConnection = (HttpURLConnection) url.openConnection();
    urlConnection.setRequestMethod(method); 
    urlConnection.setConnectTimeout(TIMEOUT * 1000);
    urlConnection.setChunkedStreamingMode(0);

    // Set HTTP headers                 
    String authString = "username:password";
    String base64Auth = Base64.encodeToString(authString.getBytes(), Base64.DEFAULT);
    urlConnection.setRequestProperty("Authorization", "Basic " + base64Auth);
    urlConnection.setRequestProperty("Accept", "application/json");
    urlConnection.setRequestProperty("Content-type", "application/json");

    if (method.equals("POST") || method.equals("PUT")) {
        // Set to true when posting data
        urlConnection.setDoOutput(true);

        // Write data to post to connection output stream
        OutputStream out = urlConnection.getOutputStream();
        out.write(postParameters.getBytes("UTF-8"));
    }

    try {
        // Get response
        in = new BufferedInputStream(urlConnection.getInputStream());
    }
    catch (IOException e) {
        Log.e(TAG, "Exception in getting connection input stream. in : " + in);
                    e.printStackTrace();
    }

    // Read the input stream that has response
    statusCode = urlConnection.getResponseCode();
    Log.d(TAG, "Status code : " + statusCode);
}
catch (ProtocolException pe) {
    pe.printStackTrace();
}
catch (IllegalStateException ie) {
    ie.printStackTrace();
}
catch (IOException e) {
    e.printStackTrace();
}   
finally {
    urlConnection.disconnect();
}


Посмотрите снимок экрана logcat:

logcat

Любая помощь будет оценена по достоинству. Спасибо.

4b9b3361

Ответ 1

Эта ошибка возникает из-за того, что сервер отправляет 401 (неавторизованный), но не дает заголовок WWW-Authenticate, который является подсказкой к клиент, что делать дальше. Заголовок WWW-Authenticate сообщает клиенту, какой тип аутентификации необходим (Basic или Digest). Это, вероятно, не очень полезно в безгласных http-клиентах, но то, как определяется HTTP 1.1 RFC. Ошибка возникает из-за того, что lib пытается проанализировать заголовок WWW-Authenticate, но не может.

Из RFC:

(...) Ответ ДОЛЖЕН включать поле заголовка WWW-Authenticate (раздел 14.47), содержащий вызов, применимый к запрашиваемому ресурсу. (...)

Возможные решения, если вы можете изменить сервер:

  • Добавьте поддельный заголовок "WWW-Authenticate", например: WWW-Authenticate: Basic realm="fake". Это просто обходное решение, а не решение, но оно должно работать, и клиент http удовлетворен (см. Здесь обсуждение того, что вы можете поместить в заголовок). Но будьте осторожны, чтобы некоторые клиенты-клиенты могли автоматически повторить запрос, результатом которого было несколько запросов (например, слишком часто увеличивает неверный счет входа в систему). Это наблюдалось с клиентом iOS http.
  • Как предложил громвчар в этот блог, чтобы избежать автоматической реакции на вызов, как всплывающая форма входа в браузере, вы можете используйте нестандартный метод аутентификации, например: WWW-Authenticate: xBasic realm="fake". Важным моментом является включение realm.
  • Используйте код состояния HTTP 403 вместо 401. Это семантика не то же самое и обычно при работе с логином 401 правильный ответ (см. Здесь для подробного обсуждения), но более безопасное решение с точки зрения совместимости.

Возможные решения, если вы не можете изменить сервер:

  • Как писал @ErikZ в своем сообщении вы можете использовать try & catch

    HttpURLConnection connection = ...;
    try {
        // Will throw IOException if server responds with 401.
        connection.getResponseCode(); 
    } catch (IOException e) {
        // Will return 401, because now connection has the correct internal state.
        int responsecode = connection.getResponseCode(); 
    }
    
  • Используйте другой http-клиент, например OkHttp

Ответ 2

В какую версию Android вы тестируете?

У меня возникли трудности с аутентификатором Android во время некоторых разработок на Gingerbread (я не знаю, ведет ли оно по-другому в более поздних версиях Android). Я использовал Fiddler2 для проверки HTTP-трафика между моим приложением и сервером, обнаружив, что аутентификатор не отправил строку аутентификации для каждого HTTP-запроса. Мне это было нужно.

Вместо этого я обратился к этому:

urlConnection.setRequestProperty("Authorization", "Basic " + Base64.encodeToString("userid:pwd".getBytes(), Base64.NO_WRAP ));

Это вырезание и вставка из моего кода. Обратите внимание, что urlConnection является объектом HttpURLConnection.

Ответ 3

У меня была такая же проблема на устройствах, на которых работает pre-KitKat Android, но я использовал библиотеку Volley, поэтому исправление на стороне клиента, предоставленное @for3st, не сработало для меня, мне пришлось настроить его для Volley, вот оно, надеюсь, что это поможет кому-то, кто борется с этой проблемой:

HurlStack hurlStack = new HurlStack() {
            @Override
            public HttpResponse performRequest(final Request<?> request, final Map<String, String> additionalHeaders) throws IOException, AuthFailureError {
                try {
                    return super.performRequest(request, additionalHeaders);
                } catch (IOException e) {
                    return new BasicHttpResponse(new ProtocolVersion("HTTP", 1, 1), 401, e.getMessage());
                }
            }
        };

Volley.newRequestQueue(context.getApplicationContext(), hurlStack);

Таким образом возвращается ошибка 401, и ваша политика повтора может выполнять эту работу (например, токен запроса... и т.д.). Хотя IOException может быть вызвано какой-то другой проблемой, кроме 401, поэтому вы можете выбрать синтаксический анализ сообщения об исключении для ключевого слова Authorization и вернуть другой код ответа для других.

Ответ 4

Имела ту же проблему на некоторых старых устройствах (например, Huawei Y330-U11). Правильный способ исправить это - исправить ее на стороне сервера, как указано в наиболее популярном ответе.

Однако, это действительно разочаровывает, что проблема возникает только на некоторых устройствах. И я считаю, что это происходит из-за различных реализаций "UrlConnection". Различные версии Android - разные реализации "UrlConnection".

Итак, вы можете исправить это, используя всюду "UrlConnection". Попробуйте использовать okhttp и okhttp-urlconnection.

Вот как добавить эти библиотеки в вашу конструкцию gradle:

compile 'com.squareup.okhttp:okhttp:2.5.0'
compile 'com.squareup.okhttp:okhttp-urlconnection:2.5.0'

Он решил проблему для меня на этих устаревших устройствах. (Мне пришлось использовать OkClient для RetoFit RestAdapter)

P.S. Последние андроиды на момент написания статьи используют старую версию библиотеки OKHTTP внутри себя как реализацию "UrlConnection" (с обновленными именами пакетов), поэтому она кажется довольно твердой.

Ответ 5

Вы можете использовать что-то вроде этого

catch (IOException ex) {
        if(ex.getMessage().toString().toLowerCase().equals(("No authentication challenges found").toLowerCase()))
// re-generate the authentication Token
             }