当error.networkResponse为null时,Android Volley中的Http状态码是什么?

45

我在Android平台上使用Google Volley。 在onErrorResponse中,error参数返回了一个空的networkResponse,这是我的一个问题。 对于我正在使用的RESTful API,我需要确定Http状态码,通常是401(SC_UNAUTHORIZED)或500(SC_INTERNAL_SERVER_ERROR),我可以偶尔通过检查来确定:

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

这会抛出NullPointerException,因为networkResponse为空。

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

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


在 Kotlin 中,VolleyError.networkResponse 不可为空,但仍然为 null - 这真的很令人沮丧。 - rmirabelle
8个回答

84

那么,在 onErrorResponse 中,我如何确保 error.networkResponse 是非空的呢?

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

@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<T>类的工具箱类之一,并且您不想混淆parseNetworkResponse(NetworkResponse networkResponse)方法的实现,请继续重写该方法,但通过super.parseNetworkResponse(networkResponse)返回超类的实现。

例如: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。请参见链接


当服务器发送状态码403时,parseNetworkResponse方法不会被调用。 - Dr.jacky
如果您尝试访问内部类中的任何内容,系统会要求您将该变量设置为final。在我看来这并不是很好。 - vsecades

24

401不受Volley支持

由于Volley的一个错误,即在BasicNetwork.java(134)中,在设置networkResponse值之前,为Http状态码401(HttpStatus.SC_UNAUTHORIZED)抛出异常NoConnectionError,因此无法保证error.networkResponse非空。

解决方案

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

对于这个Http状态码,在Volley错误处理程序中,error.networkResponse是非空的:public void onErrorResponse(VolleyError error)。而且,error.networkResponse.httpStatusCode正确地返回了HttpStatus.SC_FORBIDDEN

其他建议

Rperryng提出扩展Request<T>类可能会提供一个解决方案,这是一个创造性和优秀的想法。非常感谢您提供详细的示例。我们发现最佳解决方案是使用解决方案,因为我们很幸运能够控制Web服务API。

如果我不能轻松更改服务器,则可以选择仅在BasicNetwork.java的一个位置上修复Volley代码。


5
Volley支持HTTP 401未经授权的响应。但是,此响应必须包含“WWW-Authenticate”标头字段。
如果没有此标头,401响应会导致“com.android.volley.NoConnectionError:java.io.IOException:找不到身份验证挑战”错误。
更多细节请参见:https://dev59.com/Y2Ij5IYBdhLWcg3wHhtX#25556453 如果您使用第三方API并且无权更改响应标头,则可以考虑实现自己的HttpStack,因为HurlStack引发了此异常。或者更好地,使用OkHttpStack作为HttpStack。

4

您可以修改Volley库中的performRequest me(toolbox / BasicNetwork.java)方法以捕获401未经授权的响应。(此修改后的代码还将解决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);
            }
        }
    }
}

然后在Volley错误处理程序中使用以下代码。
@Override
        public void onErrorResponse(VolleyError error) {
             if (error instanceof AuthFailureError) {
                //handler error 401 unauthorized from here
            }
        }
    })

愉快编码 :D


3

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

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;
                    }
                }

3
error.networkResponse如果设备没有网络连接(您可以通过启用飞行模式来证明),将返回null。请查看来自Volley库的相应代码片段
在查找networkResponse之前,必须先检查错误是否是NoConnectionError的实例。我不同意Volley不支持401错误,我测试过,并获得了一个非空的networkResponse对象,其中包含401状态码。请查看相应的代码此处

1

这是我检查和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();
                }

0

我手动解决了这个问题:

  1. 从Github下载Volley库并将其添加到AndroidStudio项目中

  2. 进入com.android.volley.toolbox.HurlStack

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

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接