Ошибки запроса HttpURLConnection на Android 6.0 (MarshMallow)

Я использую библиотеку Google Volley для своего проекта приложения, который нацелен на минимальный уровень API 14. Итак, библиотека Volley использует HttpURLConnection в качестве NetworkClient.

Поэтому не должно быть никаких проблем, связанных с удалением Apache HTTPClient. Тем не менее, я выполнил конфигурацию, необходимую для 6.0 Sdk, т.е. compileSdkVersion 23, build-tool-version 23.0.1 и build:gradle:1.3.1 'и даже попытался добавить useLibrary 'org.apache.http.legacy'. Обновил то же самое для проекта библиотеки Volley в моем приложении.

Недавно я попытался запустить свое приложение на Android 6.0 (MarshMallow), мой проект компилируется и запускается. Но те запросы, которые требуют заголовков аутентификации, не работают в MarshMallow:

BasicNetwork.performRequest: Неожиданный код ответа 401 com.android.volley.AuthFailureError

Однако то же самое работает на всех уровнях API ниже 23.

Я много раз проверял заголовки. Как ни странно, те запросы, которые не требуют аутентификации, дают ответ с 200 OK.

Прямо сейчас я совершенно не знаю, что нарушает запросы, кто-нибудь знает, что изменилось в новой версии, что запрос HttpURLConnection не работает только для уровня API 23? Кто-нибудь еще использует Volley и сталкивается с подобной проблемой?

Вот мой класс CustomRequest

public class CustomRequest extends Request<Void> {

int id, cmd;
Map<String, String> params;
BaseModel model;

public CustomRequest(int method, int cmd, String url, Map<String, String> params, int id, BaseModel model) {
    super(method, url, null);
    this.id = id;
    this.cmd = cmd;
    this.params = params;
    this.model = model;

    if (method == Method.GET) {
        setUrl(buildUrlForGetRequest(url));
    }

    Log.v("Volley", "Making request to: " + getUrl());
}

private String buildUrlForGetRequest(String url) {
    if (params == null || params.size() == 0) return url;

    StringBuilder newUrl = new StringBuilder(url);
    Set<Entry<String, String>> paramPairs = params.entrySet();
    Iterator<Entry<String, String>> iter = paramPairs.iterator();

    newUrl.append("?");
    while (iter.hasNext()) {
        Entry<String, String> param = iter.next();
        newUrl
            .append(param.getKey())
            .append("=")
            .append(param.getValue());
        if (iter.hasNext()) newUrl.append("&");
    }

    return newUrl.toString();
}

@Override
public Map<String, String> getHeaders() throws AuthFailureError {
    Map<String, String> headers = new HashMap<String, String>();

    headers.put("X-Api-Version", Contract.API_VERSION);
    headers.put("X-Client", "android");
    String accessToken = APP.getInstance().getToken();
    if (!accessToken.equals("")) {
        headers.put("Authorization", "Bearer " + accessToken);    
    }

    return headers;
}

@Override
protected Response<Void> parseNetworkResponse(NetworkResponse response) {
    Exception ex;
    try {
        String jsonString = new String(
                response.data, HttpHeaderParser.parseCharset(response.headers));
        JsonNode json = new ObjectMapper().readTree(jsonString);
        if (model != null) model.parse(id, json);
        EventBus.getDefault().post(new EventResponse(cmd, true, null));
        return Response.success(null, HttpHeaderParser.parseCacheHeaders(response)); //Doesn't return anything. BaseModel.parse() does all the storage work.
    } catch (NoMoreDataException e) {
        ex = e;
        EventBus.getDefault().post(new NoMoreDataModel(cmd, e));
        EventBus.getDefault().post(new EventResponse(cmd, false, null));
        return Response.success(null, HttpHeaderParser.parseCacheHeaders(response));
    } catch (Exception e) {
        ex = e;
        Log.e("CustomRequest", Log.getStackTraceString(e));

        String message = APP.getInstance().getString(R.string.failedRequest);
        if (!TextUtils.isEmpty(e.getMessage()))
            message = e.getMessage();
        EventBus.getDefault().post(new ErrorEventModel(cmd, message, e));
        return Response.error(new ParseError(ex));
    }
}

@Override
protected Map<String, String> getParams() throws AuthFailureError {
    return params;
}

@Override
protected void deliverResponse(Void response) {
    Log.v("Volley", "Delivering result: " + getUrl());
}

@Override
public void deliverError(VolleyError error) {
    Log.e("CustomRequest", "Delivering error: Request=" + getUrl() + " | Error=" + error.toString());

    String message = APP.getInstance().getString(R.string.failedRequest);
    EventBus.getDefault().post(new ErrorEventModel(cmd, message, error));
}

}

Единственное различие, которое я нашел между Api 23 и другими, — это HostNameVerifier. Для уровня API 23: com.android.okhttp.internal.tls.OkHostnameVerifier Для уровня API ‹23: javax.net.ssl.DefaultHostnameVerifier.


person Priya Mall    schedule 22.10.2015    source источник
comment
Пожалуйста, опубликуйте свой код и информацию о logcat, если она доступна.   -  person BNK    schedule 23.10.2015
comment
Извините, моя репутация не позволяет мне публиковать больше кода. Logcat показывает только приведенную выше ошибку фрагмента кода. Пожалуйста, посмотрите мои выводы, может ли это быть причиной проблемы.   -  person Priya Mall    schedule 23.10.2015
comment
Я думаю, вы можете попробовать жестко закодировать токен доступа (вы можете использовать Postman, чтобы получить новый токен, а затем вставить его в свой код). Мой проект залпа с токеном на предъявителя работает в эмуляторе API23. Убедитесь, что у вас есть действительный / неистекший токен :)   -  person BNK    schedule 23.10.2015
comment
Пробовал жесткое кодирование. Не повезло :( После того, как все эти запросы работают на API ниже 23 с той же кодовой базой и токеном, который читается правильно.   -  person Priya Mall    schedule 23.10.2015
comment
У вас есть контроль над приложением на стороне сервера? Если да, попробуйте отладить его. Является ли веб-API Asp.Net?   -  person BNK    schedule 23.10.2015
comment
Используйте «Log.i (LOG, accessToken);» ниже 'String accessToken = APP.getInstance().getToken();' затем опубликуйте новый logcat, пожалуйста   -  person BNK    schedule 23.10.2015
comment
Извините, проблема с репутацией. Ошибка Logcat: D/Volley: [236] BasicNetwork.logSlowRequests: ответ HTTP на запрос=‹[] api-qa.theteamie.com/app/ 0x7d26a972 NORMAL 4› [lifetime=7600], [size=81], [rc=401], [retryCount=0] E /Volley: [236] BasicNetwork.performRequest: Неожиданный код ответа 401 для api-qa.theteamie.com/app/ I/LOG: a2457965e8b577522ec3f80653d19b9d46ac75bd   -  person Priya Mall    schedule 23.10.2015
comment
Нет доступа к серверному приложению. Использование REST API. У меня есть сильное ощущение, что OkHostNameVerifier не может прочитать заголовок авторизации. Потому что выполняются те запросы, которым не нужен этот заголовок. Не могли бы вы проверить в своем проекте Volley, что такое HttpURLConnection.getDefaultHostnameVerifier для Api 23. Возможно ли это для Вы?   -  person Priya Mall    schedule 23.10.2015
comment
Поскольку у вас есть ошибка аутентификации, где ваш код для добавления заголовка аутентификации?   -  person BladeCoder    schedule 23.10.2015
comment
На стороне сервера HTTPS? Если НЕТ, я не думаю, что это из-за HostNameVerifier. Если ДА, вам нужно вызвать setHostNameVerifier, прочитайте мой ответ на поддержка ssl"> stackoverflow.com/questions/32673568/   -  person BNK    schedule 24.10.2015
comment
@BladeCoder Пожалуйста, посмотрите метод getHeaders() в приведенном выше коде. Там я добавляю заголовок авторизации со значением токена доступа.   -  person Priya Mall    schedule 24.10.2015
comment
@BNK Да, это HTTPS, и я уже реализовал setHostNameVerifier. Не знаю, что не так :( Но спасибо за попытку помочь!   -  person Priya Mall    schedule 24.10.2015
comment
Если ваше серверное приложение опубликовано в Интернете, пожалуйста, сообщите мне его URL-адрес и основные параметры, чтобы я мог проверить больше.   -  person BNK    schedule 24.10.2015
comment
HurlStack (HttpURLConnection) от Volley не читает заголовок авторизации для Api 23, поэтому каждый раз 401 UnAuthorized для запросов, которым нужен этот заголовок. Пытался вызывать запросы, используя тот же токен доступа, который передается через почтальона, работает нормально. Таким образом, нет проблем с токеном доступа. Но почему он не устанавливает заголовок авторизации. Есть ли способ выяснить это?   -  person Priya Mall    schedule 25.10.2015
comment
Я не понимаю, почему вы это сказали :), вы можете найти map.putAll(request.getHeaders()); map.putAll(additionalHeaders); внутри performRequest HurlStack.java   -  person BNK    schedule 29.10.2015
comment
@BNK Я знаю, что это была странная проблема, с которой я столкнулся, и вы сказали, что она работает нормально для вас. Но нашел решение, которое работает для всех новых и старых версий. Вы можете проверить мой ответ. Спасибо :)   -  person Priya Mall    schedule 29.10.2015


Ответы (1)


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

Итак, исправление, которое сработало для меня:

Создан и добавлен новый пользовательский заголовок X-Proxy-No-Redirect => 1 и передан вместе с другими заголовками.

Теория, стоящая за этим:

Существует сервер API, на который мы отправляем запрос, затем сервер API перенаправляет запрос на соответствующий сайт на основе токена oAuth. Во время перенаправления. Чтобы произошло перенаправление, есть два способа сделать это.

1 - Мы просто отправляем ответ вызывающему абоненту о перенаправлении на определенную страницу. Затем вызывающая сторона (сетевая библиотека) позаботится о перенаправлении

2 - сервер API сам сделает запрос на перенаправление и получит ответ, а затем передаст вызывающему

Ранее мы использовали Метод 1.

На Android 6.0 - сетевая библиотека (Volley), похоже, не устанавливает все заголовки при выполнении запроса на перенаправление. Как только этот новый заголовок будет установлен, метод 2 вступит в силу.

P.S Это исправление было применимо к моему проекту приложения, возможно, не к другим. Просто указав, что было не так и что мне помогло.

person Priya Mall    schedule 29.10.2015