Intercept and retry call by means of OkHttp Interceptors - java

I need to retry request inside of OkHttp Interceptor. For example there is incoming request which needs Authorization token. If Authorization token is expired, server returns response with 403 code. In this case I am retrieving a new token and trying to make call again by using the same chain object.
But OkHttp throws an exception, which states that you cannot make two requests with the same chain object.
java.lang.IllegalStateException: network interceptor org.app.api.modules.ApplicationApiHeaders#559da2 must call proceed() exactly once
I wonder if there is a clean solution to this problem of retrying network request inside of OkHttp Interceptor?
public final class ApplicationApiHeaders implements Interceptor {
private static final String AUTHORIZATION = "Authorization";
private TokenProvider mProvider;
public ApplicationApiHeaders(TokenProvider provider) {
mProvider = provider;
}
#Override
public Response intercept(Chain chain) throws IOException {
Token token = mProvider.getApplicationToken();
String bearerToken = "Bearer " + token.getAccessToken();
System.out.println("Token: " + bearerToken);
Request request = chain.request();
request = request.newBuilder()
.addHeader(AUTHORIZATION, bearerToken)
.build();
Response response = chain.proceed(request);
if (!response.isSuccessful() && isForbidden(response.code())) {
Token freshToken = mProvider.invalidateAppTokenAndGetNew();
String freshBearerToken = freshToken.getAccessToken();
Request newRequest = chain.request();
newRequest = newRequest.newBuilder()
.addHeader(AUTHORIZATION, freshBearerToken)
.build();
response = chain.proceed(newRequest);
}
return response;
}
private static boolean isForbidden(int code) {
return code == HttpURLConnection.HTTP_FORBIDDEN;
}
}

Use .interceptors() instead of .networkInterceptors() which are allowed to call .proceed() more than once.
For more information see: https://square.github.io/okhttp/interceptors/

Related

What is the best practices for retry mechanism while using Retrofit?

Problem Statement:
I'm using Retrofit in my application for API calls. Currently I've 20+ Retrofit Interfaces, with different Callbacks. Currently when app receives INVALID_SESSION_ID in anyone of these Interfaces (say UpdateUserAPI), I've to get new ACCESS_TOKEN, by invoking AccessTokenAPI.
Approach Suggested:
When app receives INVALID_SESSION_ID in Callback in UpdateUserAPI, invoke AccessTokenAPI to get new ACCESS_TOKEN. Upon receiving new ACCESS_TOKEN, post the actual call (with initial parameters in UpdateUserAPI) with new ACCESS_TOKEN. But this requires to save parameters in the class which implements UpdateUserAPI. Also I need to retry getting ACCESS_TOKEN only once, which should be handled.
What is the best approach to implement above requirement?
Create your own TokenInterceptor
public class TokenInterceptor implements Interceptor
Then set it to your okktpclient
Interceptor tokenInterceptor = new TokenInterceptor(provideUserLoginDao(appDatabase));
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(tokenInterceptor)
.writeTimeout(50, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();
Useful information in this post also : Refreshing OAuth token using Retrofit without modifying all calls
Create your own custom interceptor and check your token/session_id is valid or not. If your session_id is expired and then hit your updateUserAPI to get new id and set this id in header or where you want. Here is some code samples.
RefreshTokenInterceptor
public static class RInterceptor implements Interceptor {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
try {
if (response.code() == 410) {
Response r = null;
try {
r = makeTokenRefreshCall(request, chain);
} catch (JSONException e) {
e.printStackTrace();
}
return r;
}
} catch (Exception e) {
e.printStackTrace();
}
return response;
}
}
private static Response makeTokenRefreshCall(Request req, Interceptor.Chain chain) throws JSONException, IOException {
/* fetch refreshed token, some synchronous API call, whatever Because we are responsible to return new response */
refreshTokenSync();
Request newRequest;
newRequest = req.newBuilder().header("authorization", NEW_TOKEN)/*.post(req.body())*/.build();
return chain.proceed(newRequest);
}
RESTClient
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(50, TimeUnit.SECONDS)
.writeTimeout(55, TimeUnit.SECONDS)
.connectTimeout(50, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.addInterceptor(new NetworkInterceptor())
.build();

Retrofit with JWT Authentication - How to inform the interceptor about the current user?

I am using Retrofit 2.3.0 to talk to an API that uses JWT for authentication from a Spring Boot application.
To make it work, I created an Interceptor implementation:
private static class JwtAuthenticationInterceptor implements Interceptor {
private Supplier<String> jwtTokenSupplier;
private JwtAuthenticationInterceptor(Supplier<String> jwtTokenSupplier) {
this.jwtTokenSupplier = jwtTokenSupplier;
}
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request.Builder builder = original.newBuilder()
.header("Authorization",
String.format("Bearer %s", jwtTokenSupplier.get()));
Request request = builder.build();
return chain.proceed(request);
}
}
In my Spring service, I let Retrofit create an instance of the API interface in the constructor:
public MySringServiceImpl() {
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(createLoggingInterceptor())
.addInterceptor(new JwtAuthenticationInterceptor(this::createJwtToken))
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://my.remoteapi.com/api/")
.addConverterFactory(JacksonConverterFactory.create())
.client(client)
.build();
api = retrofit.create(MyRemoteApi.class);
}
So in the actual methods of my service, I use something like this:
public List<Stuff> getStuffFromApi() {
try {
List<Stuff> response = api.getStuff().execute().body();
if (response != null) {
return response;
} else {
return new ArrayList<>();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
The createJwtToken method create JWT token (Using the Java JWT library)
private String createJwtToken() {
return Jwts.builder()
.setIssuer("http://my.remoteapi.com/api/")
.setId("my-test-id")
.setIssuedAt(new Date())
.setExpiration(new Date(ZonedDateTime.now().plusSeconds(60).toEpochSecond() * 1000))
.claim("uid", "123")
.signWith(SignatureAlgorithm.HS512,
"my-very-secret-key"
.getBytes())
.compact();
}
The actual problem:
The uid claim needs to contain the id of the current user (instead of being hardcoded like it is now). I am well aware on how to get the Spring principal in the RestController and than pass that down to the service, but how would I instruct the interceptor to use the id of that principal for the call that is happening? Should I create a new Retrofit instance for each call or are there better ways to handle this?

Spring Security OAuth 1.0 flow - Consumer verification

I have an external partner that uses OAuth 1.0 to protect some resources. I need to access this resources and I would like to do this using Spring Boot and Spring Security OAuth. As I don't want to use XML configuration, I already searched for a way to set up everything via Java configuration. I found this thread that provided an example of how to do this. But serveral things regarding the OAuth 1.0 flow are not clear for me.
My partner provides four endpoints for OAuth: an endpoint that provides a consumer token, a request_token endpoint, an authorization endpoint and an access_token endpoint. With my current setup (shown below) I can get a request token and the authorization endpoint gets called. However, the authorization endpoint does not ask for confirmation, but expects as URL parameters an email and a password and, after checking the credentials, returns the following:
oauth_verifier=a02ebdc5433242e2b6e582e17b84e313
And this is where the OAuth flow gets stuck.
After reading some articles about OAuth 1.0 the usual flow is this:
get consumer token / key
get oauth token using the consumer token via request_token endpoint
redirect to authorization URL and ask the user for confirmation
redirect to consumer with verifier token
user verifier token and oauth token to get access token via access_token endpoint
First of all: steps 3 and 4 are not clear to me. I've found the Spring Security OAuth examples, but it wasn't clear to me how, after confirming the access, the user / verifier token get send back to the consumer. Could someone please explain how this is done?
Second: Given that my partners endpoint does not ask for confirmation but returns an oauth verifier right away, how can I use Spring Security OAuth with this setup? I was thinking about implementing my own authorization endpoint that calls the authorziation endpoint of my partner and then somehow makes the verifier known to my consumer, but I'm not sure how to do the latter part.
Here is the code so far (with help for the thread mentioned above; the ConsumerTokenDto has been left out as it is trivial):
Application
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Endpoint
#RestController
public class Endpoint {
#Autowired
private OAuthRestTemplate oAuthRestTemplate;
private String url = "https://....";
#RequestMapping("/public/v1/meters")
public String getMeters() {
try {
return oAuthRestTemplate.getForObject(URI.create(url), String.class);
} catch (Exception e) {
LOG.error("Exception", e);
return "";
}
}
}
OAuth configuration
#Configuration
#EnableWebSecurity
public class OAuthConfig extends WebSecurityConfigurerAdapter {
#Autowired
private RestTemplateBuilder restTemplateBuilder;
private ConsumerTokenDto consumerTokenDto;
private static final String ID = "meters";
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").permitAll();
http.addFilterAfter(this.oauthConsumerContextFilter(), SwitchUserFilter.class);
http.addFilterAfter(this.oauthConsumerProcessingFilter(), OAuthConsumerContextFilterImpl.class);
}
private OAuthConsumerContextFilter oauthConsumerContextFilter() {
OAuthConsumerContextFilter filter = new OAuthConsumerContextFilter();
filter.setConsumerSupport(this.consumerSupport());
return filter;
}
private OAuthConsumerProcessingFilter oauthConsumerProcessingFilter() {
OAuthConsumerProcessingFilter filter = new OAuthConsumerProcessingFilter();
filter.setProtectedResourceDetailsService(this.prds());
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> map = new LinkedHashMap<>();
// one entry per oauth:url element in xml
map.put(
new AntPathRequestMatcher("/public/v1/**", null),
Collections.singletonList(new SecurityConfig(ID)));
filter.setObjectDefinitionSource(new DefaultFilterInvocationSecurityMetadataSource(map));
return filter;
}
#Bean
OAuthConsumerSupport consumerSupport() {
CoreOAuthConsumerSupport consumerSupport = new CoreOAuthConsumerSupport();
consumerSupport.setProtectedResourceDetailsService(prds());
return consumerSupport;
}
#Bean
ProtectedResourceDetailsService prds() {
InMemoryProtectedResourceDetailsService service = new InMemoryProtectedResourceDetailsService();
Map<String, ProtectedResourceDetails> store = new HashMap<>();
store.put(ID, prd());
service.setResourceDetailsStore(store);
return service;
}
ProtectedResourceDetails prd() {
ConsumerTokenDto consumerToken = getConsumerToken();
BaseProtectedResourceDetails resourceDetails = new BaseProtectedResourceDetails();
resourceDetails.setId(ID);
resourceDetails.setConsumerKey(consumerToken.getKey());
resourceDetails.setSharedSecret(new SharedConsumerSecretImpl(consumerToken.getSecret()));
resourceDetails.setRequestTokenURL("https://.../request_token");
// the authorization URL does not prompt for confirmation but immediately returns an OAuth verifier
resourceDetails.setUserAuthorizationURL(
"https://.../authorize?email=mail&password=pw");
resourceDetails.setAccessTokenURL("https://.../access_token");
resourceDetails.setSignatureMethod(HMAC_SHA1SignatureMethod.SIGNATURE_NAME);
return resourceDetails;
}
// get consumer token from provider
private ConsumerTokenDto getConsumerToken() {
if (consumerTokenDto == null) {
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("client", "Client");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers);
RestTemplate restTemplate = restTemplateBuilder.setConnectTimeout(1000).setReadTimeout(1000).build();
restTemplate.getInterceptors().add(interceptor);
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
ResponseEntity<ConsumerTokenDto> response = restTemplate
.exchange("https://.../consumer_token", HttpMethod.POST, request,
ConsumerTokenDto.class);
consumerTokenDto = response.getBody();
}
return consumerTokenDto;
}
// create oauth rest template
#Bean
public OAuthRestTemplate oAuthRestTemplate() {
OAuthRestTemplate oAuthRestTemplate = new OAuthRestTemplate(prd());
oAuthRestTemplate.getInterceptors().add(interceptor);
return oAuthRestTemplate;
}
}
I think I've found a solution. The trick is to implement my own OAuthConsumerContextFilter and replace the redirect call with a direct call to the authorization endpoint. I've commented the interesting parts below (starting with //!!!!).
CustomOAuthConsumerContextFilter
public class CustomOAuthConsumerContextFilter extends OAuthConsumerContextFilter {
private static final Logger LOG = LoggerFactory.getLogger(CustomOAuthConsumerContextFilter.class);
private RestTemplateBuilder restTemplateBuilder;
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
OAuthSecurityContextImpl context = new OAuthSecurityContextImpl();
context.setDetails(request);
Map<String, OAuthConsumerToken> rememberedTokens =
getRememberMeServices().loadRememberedTokens(request, response);
Map<String, OAuthConsumerToken> accessTokens = new TreeMap<>();
Map<String, OAuthConsumerToken> requestTokens = new TreeMap<>();
if (rememberedTokens != null) {
for (Map.Entry<String, OAuthConsumerToken> tokenEntry : rememberedTokens.entrySet()) {
OAuthConsumerToken token = tokenEntry.getValue();
if (token != null) {
if (token.isAccessToken()) {
accessTokens.put(tokenEntry.getKey(), token);
} else {
requestTokens.put(tokenEntry.getKey(), token);
}
}
}
}
context.setAccessTokens(accessTokens);
OAuthSecurityContextHolder.setContext(context);
if (LOG.isDebugEnabled()) {
LOG.debug("Storing access tokens in request attribute '" + getAccessTokensRequestAttribute() + "'.");
}
try {
try {
request.setAttribute(getAccessTokensRequestAttribute(), new ArrayList<>(accessTokens.values()));
chain.doFilter(request, response);
} catch (Exception e) {
try {
ProtectedResourceDetails resourceThatNeedsAuthorization = checkForResourceThatNeedsAuthorization(e);
String neededResourceId = resourceThatNeedsAuthorization.getId();
//!!!! store reference to verifier here, outside of loop
String verifier = null;
while (!accessTokens.containsKey(neededResourceId)) {
OAuthConsumerToken token = requestTokens.remove(neededResourceId);
if (token == null) {
token = getTokenServices().getToken(neededResourceId);
}
// if the token is null OR
// if there is NO access token and (we're not using 1.0a or the verifier is not null)
if (token == null || (!token.isAccessToken() &&
(!resourceThatNeedsAuthorization.isUse10a() || verifier == null))) {
//no token associated with the resource, start the oauth flow.
//if there's a request token, but no verifier, we'll assume that a previous oauth request failed and we need to get a new request token.
if (LOG.isDebugEnabled()) {
LOG.debug("Obtaining request token for resource: " + neededResourceId);
}
//obtain authorization.
String callbackURL = response.encodeRedirectURL(getCallbackURL(request));
token = getConsumerSupport().getUnauthorizedRequestToken(neededResourceId, callbackURL);
if (LOG.isDebugEnabled()) {
LOG.debug("Request token obtained for resource " + neededResourceId + ": " + token);
}
//okay, we've got a request token, now we need to authorize it.
requestTokens.put(neededResourceId, token);
getTokenServices().storeToken(neededResourceId, token);
String redirect =
getUserAuthorizationRedirectURL(resourceThatNeedsAuthorization, token, callbackURL);
if (LOG.isDebugEnabled()) {
LOG.debug("Redirecting request to " + redirect +
" for user authorization of the request token for resource " +
neededResourceId + ".");
}
request.setAttribute(
"org.springframework.security.oauth.consumer.AccessTokenRequiredException", e);
// this.redirectStrategy.sendRedirect(request, response, redirect);
//!!!! get the verifier from the authorization URL
verifier = this.getVerifier(redirect);
//!!!! start next iteration of loop -> now we have the verifier, so the else statement below shoud get executed and an access token retrieved
continue;
} else if (!token.isAccessToken()) {
//we have a presumably authorized request token, let's try to get an access token with it.
if (LOG.isDebugEnabled()) {
LOG.debug("Obtaining access token for resource: " + neededResourceId);
}
//authorize the request token and store it.
try {
token = getConsumerSupport().getAccessToken(token, verifier);
} finally {
getTokenServices().removeToken(neededResourceId);
}
if (LOG.isDebugEnabled()) {
LOG.debug("Access token " + token + " obtained for resource " + neededResourceId +
". Now storing and using.");
}
getTokenServices().storeToken(neededResourceId, token);
}
accessTokens.put(neededResourceId, token);
try {
//try again
if (!response.isCommitted()) {
request.setAttribute(getAccessTokensRequestAttribute(),
new ArrayList<>(accessTokens.values()));
chain.doFilter(request, response);
} else {
//dang. what do we do now?
throw new IllegalStateException(
"Unable to reprocess filter chain with needed OAuth2 resources because the response is already committed.");
}
} catch (Exception e1) {
resourceThatNeedsAuthorization = checkForResourceThatNeedsAuthorization(e1);
neededResourceId = resourceThatNeedsAuthorization.getId();
}
}
} catch (OAuthRequestFailedException eo) {
fail(request, response, eo);
} catch (Exception ex) {
Throwable[] causeChain = getThrowableAnalyzer().determineCauseChain(ex);
OAuthRequestFailedException rfe = (OAuthRequestFailedException) getThrowableAnalyzer()
.getFirstThrowableOfType(OAuthRequestFailedException.class, causeChain);
if (rfe != null) {
fail(request, response, rfe);
} else {
// Rethrow ServletExceptions and RuntimeExceptions as-is
if (ex instanceof ServletException) {
throw (ServletException) ex;
} else if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
// Wrap other Exceptions. These are not expected to happen
throw new RuntimeException(ex);
}
}
}
} finally {
OAuthSecurityContextHolder.setContext(null);
HashMap<String, OAuthConsumerToken> tokensToRemember = new HashMap<>();
tokensToRemember.putAll(requestTokens);
tokensToRemember.putAll(accessTokens);
getRememberMeServices().rememberTokens(tokensToRemember, request, response);
}
}
private String getVerifier(String authorizationURL) {
HttpEntity request = HttpEntity.EMPTY;
RestTemplate restTemplate = restTemplateBuilder.setConnectTimeout(1000).setReadTimeout(1000).build();
ResponseEntity<String> response =
restTemplate.exchange(authorizationURL, HttpMethod.GET, request, String.class);
//!!!! extract verifier from response
String verifier = response.getBody().split("=")[1];
return verifier;
}
void setRestTemplateBuilder(RestTemplateBuilder restTemplateBuilder) {
this.restTemplateBuilder = restTemplateBuilder;
}
}

Add cookie to response with OkHttp3 / Retrofit

I am trying to add a header to HTTP responses. This is my Interceptor which is not working:
builder.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request addedTestRequest = request.newBuilder().addHeader("TEST", "test").build();
Response response = chain.proceed(addedTestRequest);
final Response addedTestResponse = response.newBuilder().addHeader("TEST", "test").build();
return addedTestResponse;
}
});
I can see the header TEST in outgoing traffic (requests) but not in incoming traffic (responses). Do you know what is the problem or is there another way to add headers to responses?
Note: I am using franmontiel/PersistentCookieJar with OkHttp3 / Retrofit, if that influences anything.
I suspect you’re logging the response before your additional headers are added. If you reorder the interceptors you’ll most likely see the response header where you expect it, but you’ll lose the request header.
Looking at this overview might help you to understand which interceptors see which values.
The request headers and response headers are different. I'm not familiar with that cookie store but this test explain how to use cookies in okHtttp.
https://github.com/square/okhttp/blob/c581f5ddc6a091e36e745a44ca787d903e32df51/okhttp-tests/src/test/java/okhttp3/CookiesTest.java#L78
You can simply add two interceptors into your OkHttp instance, which will do next operations:
scan cookies from the responses;
save found cookies into the prefs;
attach cookies from the prefs into the requests;
Of course, you can customize this logic according to your tasks.
public class AddCookiesInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request.Builder builder = chain.request().newBuilder();
HashSet<String> preferences = (HashSet) Preferences.getDefaultPreferences().getStringSet(Preferences.PREF_COOKIES, new HashSet<>());
for (String cookie : preferences) {
builder.addHeader("Cookie", cookie);
}
return chain.proceed(builder.build());
}
}
public class ReceivedCookiesInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
if (!originalResponse.headers("Set-Cookie").isEmpty()) {
HashSet<String> cookies = new HashSet<>();
for (String header : originalResponse.headers("Set-Cookie")) {
cookies.add(header);
}
Preferences.getDefaultPreferences().edit()
.putStringSet(Preferences.PREF_COOKIES, cookies)
.apply();
}
return originalResponse;
}
}
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.interceptors().add(new AddCookiesInterceptor());
okHttpClient.interceptors().add(new ReceivedCookiesInterceptor());

Redo previous request when the first fails with 401 Unauthorized

I'm building an Android application that will fetch data from a REST API.
To make the requests I'm using Retrofit together with Otto.
For all my requests I add a RequestInterceptor that will add a header (Authorization) to all my requests.
In the RequestInterceptor I'm calling a method to the my current access_token then I populate the header to the request.
RequestInterceptor requestInterceptor = new RequestInterceptor() {
#Override
public void intercept(RequestFacade request) {
Token token = TokenPreferences.getToken();
request.addHeader("Authorization", token.getTokenType() + " " + token.getAccessToken());
}
};
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("https://example.com")
.setRequestInterceptor(requestInterceptor)
.build();
...
This works fine until the access_token has expired, then the request will fail with HTTP status 401 Unauthorized.
When this happens, I want to make a new request to get a new access_token from my refresh_token I got and then do the first request again.
I'm not really sure how to make that work.
Try a com.squareup.okhttp.Authenticator. As far as I can tell, this is preferable to com.squareup.okhttp.Interceptor (which you'll find suggested elsewhere), because it will only kick in for unauthorized requests. Here's a basic example:
public class ApiAuthenticator implements Authenticator {
#Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
for (Challenge challenge : response.challenges()) {
if (challenge.getScheme().equals("Bearer")) {
String authToken = // Refresh the token here
if (authToken != null) {
return response.request().newBuilder()
.header("Authorization", "Bearer " + authToken)
.build();
}
}
}
return null;
}
#Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
return null;
}
}
You can attach it to your client like this:
okHttpClient.setAuthenticator(new ApiAuthenticator());
Be aware that if you're using Retrofit to refresh your token and the token is invalid, you might get unexpected, hard-to-debug behavior for 403 codes, but the solution is just to use a try/catch block.
try {
token = oauthService.refreshAccessToken(args);
} catch (RetrofitError error) {
// Do something that leads to login
}
Retry mechanizm is not implemented in Retrofit. It will be in v2. (Retrying requests manually)
You should implement retrying by recursive call from onFailure() callback as Deepack suggested.
I am facing the same issue and I currently don't know how to retry my request after it failed due to a Unauthorized error.
Although #Yuriy Ashaev mentioned that the retry mechanism should be part of the v2.0 Retrofit version, this should be dedicated only to 5xx errors (see Request Object draft description here) and attempting to retry a request that failed for another reason will raise an exception.
As of now, you can still add your own ErrorHandler to your RestAdapter and catch the Unauthorized error to fetch a refresh token. Here is a way to achieve this:
class RestErrorHandler implements retrofit.ErrorHandler {
#Override
public Throwable handleError(RetrofitError cause) {
Response r = cause.getResponse();
if (r != null && r.getStatus() == 401) {
Token token = mTokenProvider.fetchToken();
if (token != null) {
addHeader(HEADER_AUTHORIZATION, token.getToken_type() + " " + token.getAccess_token());
}
// Todo: Relaunch previous request
return cause;
}
return cause;
}
}
However I don't see any way to retry the failed request from this point other than in all your failure() request callback which will be called when returning the RetrofitError from your ErrorHandler...
I would really appreciate if someone could point us how you could retry the failed request from the ErrorHandler as the response only point to f-the request url.

Categories

Resources