error.networkResponse 为空时 Android Volley 中的 Http 状态代码

Posted

技术标签:

【中文标题】error.networkResponse 为空时 Android Volley 中的 Http 状态代码【英文标题】:Http Status Code in Android Volley when error.networkResponse is null 【发布时间】:2014-05-21 19:07:18 【问题描述】:

我在 android 平台上使用 Google Volley。 我遇到了一个问题,其中onErrorResponse 中的error 参数返回一个空值networkResponse 对于我使用的 RESTful API,我需要确定通常以 401 (SC_UNAUTHORIZED) 或 500 (SC_INTERNAL_SERVER_ERROR) 的形式到达的 Http 状态代码,我偶尔可以通过以下方式检查:

final int httpStatusCode = error.networkResponse.statusCode;
if(networkResponse == HttpStatus.SC_UNAUTHORIZED) 
    // Http status code 401: Unauthorized.

这会引发NullPointerException,因为networkResponse 为空。

如何确定函数onErrorResponse中的Http状态码?

或者,我如何确保error.networkResponseonErrorResponse 中不为空?

【问题讨论】:

【参考方案1】:

或者,我如何确保 error.networkResponse 在 onErrorResponse?

我的第一个想法是检查对象是否为空。

@Override
public void onErrorResponse(VolleyError error) 
    NetworkResponse networkResponse = error.networkResponse;
    if (networkResponse != null && networkResponse.statusCode == HttpStatus.SC_UNAUTHORIZED) 
        // HTTP Status Code: 401 Unauthorized
    

或者,您也可以尝试通过扩展Request 类并覆盖parseNetworkResponse 来获取状态码。

例如,如果扩展抽象 Request<T>

public class GsonRequest<T> extends Request<T> 

    ...
    private int mStatusCode;

    public int getStatusCode() 
        return mStatusCode;
    
    ...

    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) 

        mStatusCode = response.statusCode;
        try 
            Log.d(TAG, "[raw json]: " + (new String(response.data)));
            Gson gson = new Gson();
            String json = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
            return Response.success(gson.fromJson(json, mClazz),
                HttpHeaderParser.parseCacheHeaders(response));

         catch (UnsupportedEncodingException e) 
            return Response.error(new ParseError(e));
         catch (JsonSyntaxException e) 
            return Response.error(new ParseError(e));
        
    
    ...

或者,如果您正在使用已经扩展了抽象 Request&lt;T&gt; 类的工具箱类之一,并且您不想混淆 parseNetworkResponse(NetworkResponse networkResponse) 的实现,请继续覆盖该方法但通过 @ 返回超级的实现987654329@

例如StringResponse

public class MyStringRequest extends StringRequest 

    private int mStatusCode;

    public MyStringRequest(int method, String url, Listener<String> listener,
            ErrorListener errorListener) 
        super(method, url, listener, errorListener);
    

    public int getStatusCode() 
        return mStatusCode;
    

    @Override
    protected Response<String> parseNetworkResponse(NetworkResponse response) 
        mStatusCode = response.statusCode;
        return super.parseNetworkResponse(response);
    

用法:

public class myClazz extends FragmentActivity 


    private Request mMyRequest;
    ...

    public void makeNetworkCall() 
    mMyRequest = new MyNetworkRequest(
            Method.GET, 
            BASE_URL + Endpoint.USER,
            new Listener<String>() 

                @Override
                public void onResponse(String response) 
                    // Success

                
            , 
            new ErrorListener() 

                @Override
                public void onErrorResponse(VolleyError error) 
                    if (mMyRequest.getStatusCode() == 401) 
                        // HTTP Status Code: 401 Unauthorized
                    
                
            );

    MyVolley.getRequestQueue().add(request);

当然,覆盖内联方法的选项也是可用的

public class MyClazz extends FragmentActivity 

    private int mStatusCode;

    ...

    public void makeNetworkCall() 

        StringRequest request = new StringRequest(
                Method.GET, 
                BASE_URL + Endpoint.USER,
                new Listener<String>() 

                    @Override
                    public void onResponse(String response) 
                        // Success

                    
                , 
                new ErrorListener() 

                    @Override
                    public void onErrorResponse(VolleyError error) 
                        if (mStatusCode == 401) 
                            // HTTP Status Code: 401 Unauthorized
                        
                    
                ) 

                    @Override
                    protected Response<String> parseNetworkResponse(NetworkResponse response) 
                        mStatusCode = response.statusCode;
                        return super.parseNetworkResponse(response);
                    
                ;
    MyVolley.getRequestQueue.add(request);

更新:HttpStatus 已弃用。请改用HttpURLConnection。见Link。

【讨论】:

当服务器发送 statusCode 403 时,从未调用过 parseNetworkResponse 方法。 如果您尝试从内部类访问任何内容,您会被要求将该变量设为 final。不是很好恕我直言【参考方案2】:

Volley 不支持 401

事实证明,如果不修改 Google Volley 代码,就不可能保证 error.networkResponse 不为空,因为 Volley 中的一个错误会引发 BasicNetwork 中 Http 状态代码 401 (HttpStatus.SC_UNAUTHORIZED) 的异常 NoConnectionError .java (134) 之前设置networkResponse 的值。

解决方法

在这种情况下,我们的解决方案不是修复 Volley 代码,而是修改 Web 服务 API 以针对特定情况发送 Http 错误代码 403 (HttpStatus.SC_FORBIDDEN)。

对于此 Http 状态代码,error.networkResponse 的值在 Volley 错误处理程序中为非空值:public void onErrorResponse(VolleyError error)。并且,error.networkResponse.httpStatusCode 正确返回 HttpStatus.SC_FORBIDDEN

其他建议

Rperryng 关于扩展Request&lt;T&gt; 类的建议可能提供了一个解决方案,并且是一个有创意的绝妙想法。非常感谢您提供详细的示例。我发现我们案例的最佳解决方案是使用变通方法,因为我们很幸运能够控制 Web 服务 API。

如果我无法在服务器上进行简单更改,我可能会选择在 BasicNetwork.java 中的一个位置修复 Volley 代码。

【讨论】:

【参考方案3】:

Volley 支持 HTTP 401 未经授权的响应。但是这个响应必须包含“WWW-Authenticate”头域。

如果没有此标头,401 响应会导致 "com.android.volley.NoConnectionError: java.io.IOException: No authentication challenges found" 错误。

更多详情:https://***.com/a/25556453/860189

如果您使用 3rd 方 API 并且无权更改响应标头,您可以考虑实现自己的 HttpStack,因为 HurlStack 会抛出此异常。或者更好的是,使用 OkHttpStack 作为 HttpStack。

【讨论】:

【参考方案4】:

您可以修改 volley 库的 performRequest me(toolbox/BasicNetwork.java) 方法来捕获 401 Unauthorized 响应。 (修改后的代码也将解决volley的http-> https重定向问题)

 @Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError 
    long requestStart = SystemClock.elapsedRealtime();
    while (true) 
        HttpResponse httpResponse = null;
        byte[] responseContents = null;
        Map<String, String> responseHeaders = Collections.emptyMap();
        try 
            // Gather headers.
            Map<String, String> headers = new HashMap<String, String>();
            addCacheHeaders(headers, request.getCacheEntry());
            httpResponse = mHttpStack.performRequest(request, headers);
            StatusLine statusLine = httpResponse.getStatusLine();
            int statusCode = statusLine.getStatusCode();

            responseHeaders = convertHeaders(httpResponse.getAllHeaders());
            // Handle cache validation.
            if (statusCode == HttpStatus.SC_NOT_MODIFIED) 

                Entry entry = request.getCacheEntry();
                if (entry == null) 
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
                            responseHeaders, true,
                            SystemClock.elapsedRealtime() - requestStart);
                

                // A HTTP 304 response does not have all header fields. We
                // have to use the header fields from the cache entry plus
                // the new ones from the response.
                // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
                entry.responseHeaders.putAll(responseHeaders);
                return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
                        entry.responseHeaders, true,
                        SystemClock.elapsedRealtime() - requestStart);
            

            // Handle moved resources
            if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) 
                String newUrl = responseHeaders.get("Location");
                request.setUrl(newUrl);
            



            // Some responses such as 204s do not have content.  We must check.
            if (httpResponse.getEntity() != null) 
                responseContents = entityToBytes(httpResponse.getEntity());
             else 
                // Add 0 byte response as a way of honestly representing a
                // no-content request.
                responseContents = new byte[0];
            

            // if the request is slow, log it.
            long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
            logSlowRequests(requestLifetime, request, responseContents, statusLine);

            if (statusCode < 200 || statusCode > 299) 
                throw new IOException();
            
            return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
                    SystemClock.elapsedRealtime() - requestStart);
         catch (SocketTimeoutException e) 
            attemptRetryOnException("socket", request, new TimeoutError());
         catch (ConnectTimeoutException e) 
            attemptRetryOnException("connection", request, new TimeoutError());
         catch (MalformedURLException e) 
            throw new RuntimeException("Bad URL " + request.getUrl(), e);
         catch (IOException e) 
            int statusCode = 0;
            NetworkResponse networkResponse = null;
            if (httpResponse != null) 
                statusCode = httpResponse.getStatusLine().getStatusCode();
             else 
                throw new NoConnectionError(e);
            
            if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
                    statusCode == HttpStatus.SC_MOVED_TEMPORARILY) 
                VolleyLog.e("Request at %s has been redirected to %s", request.getUrl(), request.getUrl());
             else 
                VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
                if (statusCode==HttpStatus.SC_FORBIDDEN) 
                    throw new VolleyError("403");
                else if (statusCode == HttpStatus.SC_UNAUTHORIZED) 
                    attemptRetryOnException("auth",
                            request, new AuthFailureError(""));
                
            
            if (responseContents != null) 
                networkResponse = new NetworkResponse(statusCode, responseContents,
                        responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
                if (statusCode == HttpStatus.SC_UNAUTHORIZED) 
                    attemptRetryOnException("auth",
                            request, new AuthFailureError(networkResponse));
                 else if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
                        statusCode == HttpStatus.SC_MOVED_TEMPORARILY) 
                    attemptRetryOnException("redirect",
                            request, new AuthFailureError(networkResponse));
                 else 
                    // TODO: Only throw ServerError for 5xx status codes.
                    throw new ServerError(networkResponse);
                
             else 
                throw new NetworkError(e);
            
        
    

然后在凌空错误处理程序中使用此代码

@Override
        public void onErrorResponse(VolleyError error) 
             if (error instanceof AuthFailureError) 
                //handler error 401 unauthorized from here
            
        
    )

编码愉快:D

【讨论】:

【参考方案5】:

可以接收以下格式的网络响应

NetworkResponse response = error.networkResponse;
                if(response != null && response.data != null)
                    switch(response.statusCode)
                        case 403:
                            json = new String(response.data);
                            json = trimMessage(json, "error");
                            if(json != null) displayMessage(json);
                            break;
                    
                

【讨论】:

【参考方案6】:

如果设备没有网络连接,error.networkResponse 将是 null(您可以通过启用飞行模式来证明这一点)。查看 Volley 库中对应的code fragment。

在查找networkResponse 之前,您必须检查错误是否是NoConnectionError 的实例。我不能同意,Volley 不支持 401 错误,我对其进行了测试并得到了一个非空的 networkResponse 对象,并返回了 401 状态码。看对应代码here。

【讨论】:

【参考方案7】:

这就是我检查和 grep 错误的方式。

                // TimeoutError => most likely server is down or network is down.
                Log.e(TAG, "TimeoutError: " + (e instanceof TimeoutError));

                Log.e(TAG, "NoConnectionError: " + (e instanceof NoConnectionError));
                /*if(error.getCause() instanceof UnknownHostException ||
                    error.getCause() instanceof EOFException ) 
                    errorMsg = resources.getString(R.string.net_error_connect_network);
                 else 
                    if(error.getCause().toString().contains("Network is unreachable")) 
                        errorMsg = resources.getString(R.string.net_error_no_network);
                     else 
                        errorMsg = resources.getString(R.string.net_error_connect_network);
                    
                */

                Log.e(TAG, "NetworkError: " + (e instanceof NetworkError));
                Log.e(TAG, "AuthFailureError: " + (e instanceof AuthFailureError));
                Log.e(TAG, "ServerError: " + (e instanceof ServerError));
                //error.networkResponse.statusCode

                // inform dev
                Log.e(TAG, "ParseError: " + (e instanceof ParseError));
                //error.getCause() instanceof JsonSyntaxException

                Log.e(TAG, "NullPointerException: " + (e.getCause() instanceof NullPointerException));


                if (e.networkResponse != null) 
                    // 401 => login again
                    Log.e(TAG, String.valueOf(e.networkResponse.statusCode));

                    if (e.networkResponse.data != null) 
                        // most likely JSONString
                        Log.e(TAG, new String(e.networkResponse.data, StandardCharsets.UTF_8));

                        Toast.makeText(getApplicationContext(),
                                new String(e.networkResponse.data, StandardCharsets.UTF_8),
                                Toast.LENGTH_LONG).show();
                    
                
                else if (e.getMessage() == null) 
                    Log.e(TAG, "e.getMessage");
                    Log.e(TAG, "" + e.getMessage());

                    if (e.getMessage() != null && e.getMessage() != "")
                        Toast.makeText(getApplicationContext(),
                                e.getMessage(), Toast.LENGTH_LONG).show();
                    else
                        Toast.makeText(getApplicationContext(),
                                "could not reach server", Toast.LENGTH_LONG).show();
                
                else if (e.getCause() != null) 
                    Log.e(TAG, "e.getCause");
                    Log.e(TAG, "" + e.getCause().getMessage());

                    if (e.getCause().getMessage() != null && e.getCause().getMessage() != "")
                        Toast.makeText(getApplicationContext(),
                                e.getCause().getMessage(), Toast.LENGTH_LONG).show();
                    else
                        Toast.makeText(getApplicationContext(),
                                "could not reach server", Toast.LENGTH_LONG).show();
                

【讨论】:

【参考方案8】:

我手动处理这个问题:

    下载Volley library from github并添加到AndroidStudio项目中

    转到com.android.volley.toolbox.HurlStack

    performRequest 方法中找到setConnectionParametersForRequest(connection, request);

    最后在setConnectionParametersForRequest(connection, request); 行添加以下代码:

// for avoiding this exception : No authentication challenges found
        try 
            connection.getResponseCode();
         catch (IOException e) 
            e.printStackTrace();
        

【讨论】:

以上是关于error.networkResponse 为空时 Android Volley 中的 Http 状态代码的主要内容,如果未能解决你的问题,请参考以下文章

当 listView 为空时,setEmptyView 不显示图像

隐藏为空时隐藏

结果为空时LINQ返回啥

当条件全部为空时,查询全部,当其中一个或两个条件为空时,为空的条件不查询,sql怎么写

当值为空或为空时如何返回默认值

值为空时Chrome中的HTML输入光标位置问题