web-dev-qa-db-fra.com

Code d'état HTTP dans Android Volley lorsque error.networkResponse est null

J'utilise Google Volley sur la plate-forme Android .. J'ai un problème pour lequel le paramètre error dans onErrorResponse renvoie null networkResponse Pour l'API RESTful que j'utilise, je dois déterminer le code d'état HTTP. arrive souvent en tant que 401 (SC_UNAUTHORIZED) ou 500 (SC_INTERNAL_SERVER_ERROR), et je peux parfois vérifier via:

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

Cela jette une NullPointerException car networkResponse est null.

Comment puis-je déterminer le code de statut Http dans la fonction onErrorResponse?

Ou, comment puis-je m'assurer que error.networkResponse est non nul dans onErrorResponse?

40
David Manpearl

401 non pris en charge par Volley

Il s'avère qu’il est impossible de garantir que error.networkResponse est non nul sans modifier le code Google Volley en raison d’un bogue dans Volley qui lève l’exception NoConnectionError pour le code d’état Http 401 (HttpStatus.SC_UNAUTHORIZED) dans BasicNetwork.Java (134) avant le définir la valeur de networkResponse.

Solution de contournement

Au lieu de réparer le code Volley, notre solution consistait dans ce cas à modifier l’API du service Web afin qu’il envoie le code d’erreur HTTP 403 (HttpStatus.SC_FORBIDDEN) pour le cas particulier en question.

Pour ce code d'état HTTP, la valeur de error.networkResponse est non nulle dans le gestionnaire d'erreurs Volley: public void onErrorResponse(VolleyError error). Et, error.networkResponse.httpStatusCode renvoie correctement HttpStatus.SC_FORBIDDEN.

Autres suggestions

La suggestion de Rperryng d’étendre la classe Request<T> aurait pu fournir une solution. C’est une idée créative et excellente. Merci beaucoup pour l'exemple détaillé. J'ai trouvé que la solution optimale pour notre cas consiste à utiliser la solution de contournement, car nous avons la chance d'avoir le contrôle de l'API de services Web. 

Je pourrais opter pour la correction du code Volley dans un emplacement de BasicNetwork.Java si je n'avais pas accès à une simple modification sur le serveur. 

25
David Manpearl

Ou, comment puis-je m'assurer que error.networkResponse est non nul dans onErrorResponse?

Ma première pensée serait de vérifier si l'objet est nul.

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

Vous pouvez également essayer de saisir le code d'état en étendant la classe Request et en remplaçant parseNetworkResponse

Par exemple, si vous étendez la classe abstraite 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));
        }
    }
    ...
}

Ou, si vous utilisez l'une des classes de la boîte à outils qui étendent déjà la classe abstraite Request<T> et que vous ne voulez pas embrouiller l'implémentation pour parseNetworkResponse(NetworkResponse networkResponse), continuez de surcharger la méthode mais renvoyez l'implémentation du super via super.parseNetworkResponse(networkResponse)

par exemple. 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);
    }
}

usage:

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

Bien sûr, l'option de remplacer la méthode en ligne est également disponible

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

Mettre à jour:
HttpStatus est obsolète. Utilisez HttpURLConnection à la place. Voir Lien .

73
rperryng

Volley prend en charge la réponse HTTP 401 non autorisée. Mais cette réponse DOIT inclure le champ d’en-tête "WWW-Authenticate". 

Sans cet en-tête, la réponse 401 provoque une erreur "com.Android.volley.NoConnectionError: Java.io.IOException: No authentication challenges found".

Pour plus de détails: https://stackoverflow.com/a/25556453/860189

Si vous utilisez des API tierces et que vous n'avez pas le droit de modifier l'en-tête de la réponse, vous pouvez envisager d'implémenter votre propre HttpStack à cause de cette exception émise par HurlStack. Ou mieux, utilisez OkHttpStack en tant que HttpStack.

5
Tolga Okur

Le error.networkResponse sera null, si le périphérique n'a pas de connexion réseau (vous pouvez le vérifier en activant le mode avion). Regardez le fragment de code correspondant de la bibliothèque Volley.

Vous devez alors vérifier si l'erreur est une instance de NoConnectionError avant de rechercher networkResponse. Je ne peux pas accepter le fait que l'erreur 401 n'est pas prise en charge par Volley. Je l'ai testée et j'ai récupéré un objet networkResponse non null avec le code d'état 401. Regardez le code correspondant ici .

3
artkoenig

La réponse du réseau peut être reçue dans le format suivant

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
Uday Nayak

Vous pouvez modifier la méthode performRequest me (toolbox/BasicNetwork.Java) de la bibliothèque de volées pour capturer une réponse 401 non autorisée. (Ce code modifié résoudra également le problème de redirection de volée 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);
            }
        }
    }
}

puis dans le gestionnaire d'erreur de volée utiliser ce code 

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

Code heureux: D

2

C'est comment je vérifie et erreur de 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();
                }
1
aung

Je gère ce problème manuellement:

  1. Télécharger Volley library de github et ajouter au projet AndroidStudio

  2. Aller à la classe com.Android.volley.toolbox.HurlStack

  3. Trouver la ligne setConnectionParametersForRequest(connection, request); à l'intérieur de la méthode performRequest 

  4. Et enfin, ajoutez ce code ci-dessous de la ligne setConnectionParametersForRequest(connection, request);:

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