Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Http Status Code in Android Volley when error.networkResponse is null

I am using Google Volley on the Android platform. I am having a problem in which the error parameter in onErrorResponse is returning a null networkResponse For the RESTful API I am using, I need to determine the Http Status Code which is often arriving as 401 (SC_UNAUTHORIZED) or 500 (SC_INTERNAL_SERVER_ERROR), and I can occasionally check via:

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

This throws a NullPointerException because networkResponse is null.

How can I determine the Http Status Code in the function onErrorResponse?

Or, how can I ensure error.networkResponse is non-null in onErrorResponse?

like image 520
David Manpearl Avatar asked Apr 08 '14 20:04

David Manpearl


4 Answers

Or, how can I ensure error.networkResponse is non-null in onErrorResponse?

My first thought would be to check if the object is null.

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

Alternatively, you could also try grabbing the Status Code by extending the Request class and overriding parseNetworkResponse.

For example, if extending the abstract Request<T> class

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

Or, if you are using one of the toolbox classes that already extend the abstract Request<T> class and you don't want to muddle up the implementation for parseNetworkResponse(NetworkResponse networkResponse), continue overriding the method but return the super's implementation via super.parseNetworkResponse(networkResponse)

e.g. 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);
}

Of course, the option to override the method inline is available too

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

Update:
HttpStatus is Deprecated. Use HttpURLConnection instead. See Link.

like image 118
rperryng Avatar answered Nov 01 '22 12:11

rperryng


401 Not Supported by Volley

It turns out that it is impossible to guarantee that error.networkResponse is non-null without modifying Google Volley code because of a bug in Volley that throws the Exception NoConnectionError for Http Status Code 401 (HttpStatus.SC_UNAUTHORIZED) in BasicNetwork.java (134) prior to setting the value of networkResponse.

Work-Around

Instead of fixing the Volley code, our solution in this case was to modify the Web Service API to send Http Error Code 403 (HttpStatus.SC_FORBIDDEN) for the particular case in question.

For this Http Status Code, the value of error.networkResponse is non-null in the Volley error handler: public void onErrorResponse(VolleyError error). And, error.networkResponse.httpStatusCode correctly returns HttpStatus.SC_FORBIDDEN.

Other-Suggestions

Rperryng's suggestion of extending the Request<T> class may have provided a solution, and is a creative and excellent idea. Thank you very much for the detailed example. I found the optimal solution for our case is to use the work-around because we are fortunate enough to have control of the web services API.

I might opt for fixing the Volley code in one location within BasicNetwork.java if I did not have access to making a simple change at the server.

like image 36
David Manpearl Avatar answered Nov 01 '22 12:11

David Manpearl


Volley supports HTTP 401 Unauthorized response. But this response MUST include "WWW-Authenticate" header field.

Without this header, 401 response causes "com.android.volley.NoConnectionError: java.io.IOException: No authentication challenges found" error.

For more detail : https://stackoverflow.com/a/25556453/860189

If you consume 3rd party API's and have no right to change response header, you may consider to implement your own HttpStack because of this exception thrown from HurlStack. Or better, use OkHttpStack as a HttpStack.

like image 5
Tolga Okur Avatar answered Nov 01 '22 10:11

Tolga Okur


You may modify the volley library's performRequest me(toolbox/BasicNetwork.java) method to capture 401 Unauthorized response. (This modified code will also solve 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<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);
            }
        }
    }
}

then in volley error handler use this code

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

Happy coding :D

like image 4
Afjalur Rahman Rana Avatar answered Nov 01 '22 12:11

Afjalur Rahman Rana