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
?
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
.
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
.
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.
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 .
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.
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 .
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;
}
}
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
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();
}
Je gère ce problème manuellement:
Télécharger Volley library de github et ajouter au projet AndroidStudio
Aller à la classe com.Android.volley.toolbox.HurlStack
Trouver la ligne setConnectionParametersForRequest(connection, request);
à l'intérieur de la méthode performRequest
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(); }