Android 6.0 (MarshMallow) 上的 HttpURLConnnection 请求失败

Posted

技术标签:

【中文标题】Android 6.0 (MarshMallow) 上的 HttpURLConnnection 请求失败【英文标题】:HttpURLConnnection request failures on Android 6.0 (MarshMallow) 【发布时间】:2016-01-22 04:34:07 【问题描述】:

我在我的应用程序项目中使用 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

但是在所有低于 23 的 Api 级别上运行相同。

我已经检查了很多次标头。奇怪的是,那些不需要身份验证的请求给出的响应是 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 级别

【问题讨论】:

如果可用,请发布您的代码和 logcat 信息。 对不起,我的声誉不允许我发布更多代码。Logcat 仅显示上述代码 sn-p 错误。请看我的发现,这可能是问题的原因。 我认为您可以尝试硬编码访问令牌(您可以使用 Postman 获取新令牌,然后粘贴到您的代码中)。我的带有不记名令牌的 volley 项目在 API23 模拟器中工作。确保您有一个有效/未过期的令牌 :) 尝试过硬编码。没有运气:(毕竟这些请求都在 23 以下的 api 上工作,并且相同的代码库和令牌被正确读取。 您可以控制服务器端应用程序吗?如果是,请尝试调试它。是 Asp.Net Web API 吗? 【参考方案1】:

在检查了我的应用程序的服务器端后,找到了问题背后的原因。 对于 Android 6.0(MarshMallow),标题变为空,而其他版本则不是这样。

所以对我有用的修复:

创建并添加了一个新的自定义标头 X-Proxy-No-Redirect => 1 并与其他标头一起传递。

背后的理论:

我们发送请求到一个 API 服务器,然后 API 服务器根据 oAuth 令牌将请求重定向到相应的站点 重定向时 为了实现重定向,有两种方法可以做到这一点

1 - 我们只是将响应发送回调用者,说明重定向到某个页面。然后调用者(网络库)负责重定向

2 - API 服务器将自己发出重定向请求并获取响应,然后传递给调用者

之前我们使用的是方法 1。

在 Android 6.0 上 - 网络库 (Volley) 在发出重定向请求时似乎没有设置所有标头。 一旦设置了这个新的标头,方法二就会生效。

P.S此修复适用于我的应用程序项目,可能不适用于其他人。仅提供错误和对我有帮助的地方。

【讨论】:

以上是关于Android 6.0 (MarshMallow) 上的 HttpURLConnnection 请求失败的主要内容,如果未能解决你的问题,请参考以下文章