Using RoboSpice is there a way to get the HTTP Error Code out of an exception?
Asked Answered
T

5

14

I am writing an application that uses RoboSpice. In the request listener onRequestFailure( SpiceException arg0 ) is there a way to know for sure that the error was a result of a 401 HTTP Error occurred?

I have a back end service, that returns a 401 error when a token expires, when that occurs I need to prompt the user to re-enter their credentials.

Is there anyway to know that a 401 HTTP error specifically occurred?

Below is an example of my request.

   public class LookupRequest extends SpringAndroidSpiceRequest <Product> {

public String searchText;
public String searchMode;

public LookupRequest() {
    super( Product.class );
}

@Override
public Product loadDataFromNetwork() throws Exception {
    String url = String.format("%s/Lookup?s=%s&m=%s", Constants.BASE_URL, searchText, searchMode);
    Ln.d("Calling URL: %s", url);
    return getRestTemplate().getForObject(url, Product.class );
}
Tragedienne answered 13/2, 2013 at 22:31 Comment(0)
T
20

I looked over Spring-Android closer and it seems getRestTemplate().getForObject(...) throws a HttpClientErrorException when a 401 or any network error occurs.

Looking at the Robo Spice for where they catch that exception I found they catch it in RequestProcessor.java in the processRequest function. They pass the Spring-Android exception in as the throwable inside their SpiceException that inherits from Java exception class.

So you just do the following inside your RoboSpice RequestListener to see if it a 401 UNAUTHORIZED exception.

    private class MyRequestListener implements RequestListener<RESULT> {

    public void onRequestFailure( SpiceException arg0 ) {

        if(arg0.getCause() instanceof HttpClientErrorException)
        {
            HttpClientErrorException exception = (HttpClientErrorException)arg0.getCause();
            if(exception.getStatusCode().equals(HttpStatus.UNAUTHORIZED))
            {
                Ln.d("401 ERROR");
            }
            else
            {
                Ln.d("Other Network exception");
            }
        }
        else if(arg0 instanceof RequestCancelledException)
        {
            Ln.d("Cancelled");
        }
        else
        {
            Ln.d("Other exception");
        }
    };

    public void onRequestSuccess( RESULT result ) {
        Ln.d("Successful request");
    }
}
Tragedienne answered 15/2, 2013 at 16:21 Comment(4)
I'm experiencing some inconsistencies by using equals() to compare the status code and HttpStatus. Instead I started comparing them thus without problems: exception.getStatusCode().value() == HttpStatus.SC_BAD_REQUEST. Hope that helps.Meddlesome
HttpClientErrorException cannot be resolved to a type.Symmetrize
Instead of HttpClientErrorException, I'm getting a HttpResponseExceptionHolt
It is specific to Apache HTTP Client backend, isn't it?Sticky
H
2

I am using the google http client with RoboSpice and has the same issue but was easy to solve with request.setThrowExceptionOnExecuteError(false); and checking the response code on the resulting HttpResponse object

EDIT: the code snippit as requested

HttpRequest request = getHttpRequestFactory().buildPostRequest(new GenericUrl(URL), content);
request.setThrowExceptionOnExecuteError(false);
HttpResponse response = request.execute();

switch(response.getStatusCode())
    {
        case HttpStatusCodes.STATUS_CODE_UNAUTHORIZED:
            return new MyBaseResponse(responseBody);
        default:
            throw new RuntimeException("not implemented yet");
    }
Hodgson answered 20/2, 2013 at 11:5 Comment(0)
A
2

For those who can't resolve HttpClientErrorException into a type, and cannot find any documentations online, (that's me), here is my approach:

In my fragment, here is my listener:

private final class MyRequestListener extends RequestListener<MyResponse> {

  @Override
  public void onRequestFailure(SpiceException spiceException) {
    super.onRequestFailure(spiceException);
    if (spiceException instanceof NetworkException) {
      NetworkException exception = (NetworkException) spiceException;
      if (exception.getCause() instance RetrofitError) {
        RetrofitError error = (RetrofitError) exception.getCause();
        int httpErrorCode = error.getResponse().getStatus();
        // handle the error properly...
        return;
      }
    }
    // show generic error message
  }

}

Hope this maybe helpful to someone.

I would move the whole if clause into a static function so it can be reused. Just return 0 if exception doesn't match. And I haven't verify if any of the casting can be removed...

Apogeotropism answered 11/1, 2016 at 9:46 Comment(1)
RetrofitError is a class defined in Retrofit 1.6.1. If you are using 2.x, it may not be there anymore. Path: retrofit-1.6.1-sources.jar!/retrofit/RetrofitError.java public class RetrofitError extends RuntimeException {...}Apogeotropism
T
0

With google http client for java you can also intercept error responses like so:

public static class InitIntercept implements 
    HttpRequestInitializer, HttpUnsuccessfulResponseHandler {

    @Override
    public boolean handleResponse(
        HttpRequest request, 
        HttpResponse response, 
        boolean retrySupported) throws IOException {

        if (response.getStatusCode() == HttpStatusCodes.STATUS_CODE_UNAUTHORIZED) {
            ...
        }

        return false;
    }

    @Override
    public void initialize(HttpRequest request) throws IOException {
        request.setUnsuccessfulResponseHandler(this);
    }
}

and in your GoogleHttpClientSpiceService:

@Override
public HttpRequestFactory createRequestFactory() {
    return AndroidHttp
        .newCompatibleTransport().createRequestFactory(new InitIntercept());
}
Taverner answered 27/4, 2013 at 3:9 Comment(0)
H
0

It's even easier than all of the other answers.

For me @Scrotos answer was problematic, because the whole point for the 401 to be caught is to make the request to auth endpoint and then make the primary request again. So that you only return to UI with desired data or some "real" error. So it shouldn't be done inside the callback, but rather inside loadDataFromNetwork() itself.

I've done it this way:

@Override
public SubscriptionsContainer loadDataFromNetwork() {

    //...

    ResponseEntity<SubscriptionsContainer> response = null;
    try {
        response = getRestTemplate().exchange(
        //your request data                    
        );
    } catch (HttpClientErrorException e) {
        HttpStatus statusCode = e.getStatusCode();
        //check the exception, if it's 401, make call to auth and repeat loadDataFromNetwork()
    }
Hummingbird answered 15/9, 2014 at 10:10 Comment(2)
How do you deal with multiple concurrent request? This code will do multiple auth request, won't it.Sticky
It depends on your implementation of auth store. I use AccountManager. If one of the concurrent requests gets 401 it updates the token inside AccountManager and all subsequent requests will get a proper token from this point. But I never really tried concurrent requests with this codeSpew

© 2022 - 2024 — McMap. All rights reserved.