【问题标题】:Http Status Code in Android Volley when error.networkResponse is nullerror.networkResponse 为空时 Android Volley 中的 Http 状态代码
【发布时间】: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 中不为空?

【问题讨论】:

    标签: android http-status-codes android-volley


    【解决方案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://stackoverflow.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】:

                我手动处理这个问题:

                1. 下载Volley library from github并添加到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();
                        }
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2020-11-29
                  • 1970-01-01
                  • 2019-09-17
                  • 2019-10-25
                  • 2018-01-21
                  • 2016-02-28
                  • 1970-01-01
                  相关资源
                  最近更新 更多