Código de estado de Http en Android Volley cuando error.networkResponse es nulo

Estoy usando Google Volley en la plataforma Android. Tengo un problema en el que el parámetro de error en onErrorResponse devuelve una networkResponse nula networkResponse Para la API RESTful que estoy usando, necesito determinar el código de estado Http que a menudo llega como 401 (SC_UNAUTHORIZED) o 500 (SC_INTERNAL_SERVER_ERROR), y yo En ocasiones puede verificar a través de:

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

Esto arroja una NullPointerException porque networkResponse es nulo.

¿Cómo puedo determinar el código de estado Http en la función onErrorResponse ?

O bien, ¿cómo puedo asegurarme de que error.networkResponse no sea nulo en onErrorResponse ?

O bien, ¿cómo puedo asegurarme de que error.networkResponse no sea nulo en onErrorResponse?

Mi primer pensamiento sería verificar si el objeto es nulo.

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

Alternativamente, también puede intentar obtener el código de estado extendiendo la clase Request y anulando parseNetworkResponse .

Por ejemplo, si extiende la clase abstracta de Request

 public class GsonRequest extends Request { ... private int mStatusCode; public int getStatusCode() { return mStatusCode; } ... @Override protected Response 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)); } } ... } 

O bien, si está utilizando una de las clases de caja de herramientas que ya extienden la clase abstracta de Request y no desea parseNetworkResponse(NetworkResponse networkResponse) la implementación de parseNetworkResponse(NetworkResponse networkResponse) , continúe reemplazando el método pero devuelva la implementación del super.parseNetworkResponse(networkResponse) través de super.parseNetworkResponse(networkResponse)

por ejemplo, StringResponse

 public class MyStringRequest extends StringRequest { private int mStatusCode; public MyStringRequest(int method, String url, Listener listener, ErrorListener errorListener) { super(method, url, listener, errorListener); } public int getStatusCode() { return mStatusCode; } @Override protected Response parseNetworkResponse(NetworkResponse response) { mStatusCode = response.statusCode; return super.parseNetworkResponse(response); } } 

uso:

 public class myClazz extends FragmentActivity { private Request mMyRequest; ... public void makeNetworkCall() { mMyRequest = new MyNetworkRequest( Method.GET, BASE_URL + Endpoint.USER, new Listener() { @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); } 

Por supuesto, la opción para anular el método en línea también está disponible

 public class MyClazz extends FragmentActivity { private int mStatusCode; ... public void makeNetworkCall() { StringRequest request = new StringRequest( Method.GET, BASE_URL + Endpoint.USER, new Listener() { @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 parseNetworkResponse(NetworkResponse response) { mStatusCode = response.statusCode; return super.parseNetworkResponse(response); } }; MyVolley.getRequestQueue.add(request); } 

Actualizar:
HttpStatus está en HttpStatus . Use HttpURLConnection en HttpURLConnection lugar. Ver enlace .

401 No respaldado por Volley

Resulta que es imposible garantizar que error.networkResponse no sea nulo sin modificar el código de Google Volley debido a un error en Volley que arroja la excepción NoConnectionError para Http Status Code 401 ( HttpStatus.SC_UNAUTHORIZED ) en BasicNetwork.java (134) antes de establecer el valor de networkResponse .

Work-Around

En lugar de corregir el código de Volley, nuestra solución en este caso fue modificar la API del servicio web para enviar el código de error Http 403 ( HttpStatus.SC_FORBIDDEN ) para el caso particular en cuestión.

Para este código de estado Http, el valor de error.networkResponse no es nulo en el controlador de errores Volley: public void onErrorResponse(VolleyError error) . Y, error.networkResponse.httpStatusCode retorna correctamente HttpStatus.SC_FORBIDDEN .

Otras sugerencias

La sugerencia de Rperryng de extender la clase Request puede haber proporcionado una solución, y es una idea creativa y excelente. Muchas gracias por el ejemplo detallado. Encontré que la solución óptima para nuestro caso es utilizar el margen de error porque tenemos la suerte de tener el control de la API de servicios web.

Podría optar por arreglar el código de Volley en una ubicación dentro de BasicNetwork.java si no tuviera acceso a hacer un cambio simple en el servidor.

Volley admite HTTP 401 Respuesta no autorizada. Pero esta respuesta DEBE incluir el campo de encabezado “WWW-Authenticate”.

Sin este encabezado, la respuesta 401 provoca el error "com.android.volley.NoConnectionError: java.io.IOException: No authentication challenges found" .

Para más detalles: https://stackoverflow.com/a/25556453/860189

Si consume API de terceros y no tiene derecho a cambiar el encabezado de respuesta, puede considerar implementar su propia HttpStack debido a esta excepción lanzada desde HurlStack. O mejor, use OkHttpStack como HttpStack.

El error.networkResponse será null , si el dispositivo no tiene conexión de red (puede probar esto habilitando el modo avión). Mira el fragmento de código correspondiente de la biblioteca de Volley.

Entonces, debe verificar si el error es una instancia de NoConnectionError antes de buscar la networkResponse . No puedo estar de acuerdo con que Volley no admita el error 401, lo probé y obtuve un objeto networkResponse no nulo con el código de estado 401. Mira el código correspondiente aquí .

La respuesta de red se puede recibir en el siguiente formato

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

Así es como compruebo y grep error.

  // 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(); } 

Puede modificar el método performRequest me (toolbox / BasicNetwork.java) de la biblioteca de volley para capturar la respuesta 401 no autorizada. (Este código modificado también resolverá http-> https redirect problem of volley)

  @Override public NetworkResponse performRequest(Request request) throws VolleyError { long requestStart = SystemClock.elapsedRealtime(); while (true) { HttpResponse httpResponse = null; byte[] responseContents = null; Map responseHeaders = Collections.emptyMap(); try { // Gather headers. Map headers = new HashMap(); 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); } } } } 

luego en el controlador de errores de voley use este código

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

Feliz encoding: D

Manejo este problema de forma manual:

  1. Descargue la librería Volley de github y agréguela al proyecto AndroidStudio

  2. Ir a la clase com.android.volley.toolbox.HurlStack

  3. Encuentra setConnectionParametersForRequest(connection, request); línea dentro del método performRequest

  4. Y finalmente agregue estos códigos después de setConnectionParametersForRequest(connection, request); línea:

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