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();
Related
How to call multiple requests at the same time in Retrofit 2
I have 2 different api, and I want to call them at the same time. How can I do this ?
Thanks!
You could use enqueue method of retrofit2 for asynchronously calling multiple request at the same time.
Here is the documentation for the enqueue:
/**
* Asynchronously send the request and notify {#code callback} of its response or if an error
* occurred talking to the server, creating the request, or processing the response.
*/
void enqueue(Callback callback);
Here is the pseudo code how you could do that:
Call<MyResponse> call = retroService.getSomeData();
call.enqueue(new Callback<MyResponse>() {
#Override
public void onResponse(
public void onFailure(
});
Just create an observer that pass parameters of the two servers.
Help the code below
OkHttpClient okHttpClient = new OkHttpClient().newBuilder().addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Request.Builder builder = originalRequest.newBuilder().header("Authorization",
Credentials.basic("aUsername", "aPassword"));
Request newRequest = builder.build();
return chain.proceed(newRequest);
}
}).build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.example.com")
.client(okHttpClient)
.build();
Font
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?
I am trying to get the raw response using Retrofit2.0.2.
So far I tried to print the response using following line of code but it prints the address and not the exact response body.
Log.i("RAW MESSAGE",response.body().toString());
compile 'com.squareup.retrofit2:retrofit:2.0.2'
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
GitApi gitApi = retrofit.create(GitApi.class);
Call<Addresses> call = gitApi.getFeed(user);
call.enqueue(new Callback<Addresses>() {
#Override
public void onResponse(Response<Addresses> response, Retrofit retrofit) {
try {
mDisplayDetails.setText(response.body().getSuburbs().get(0).getText());
**Log.i("RAW MESSAGE",response.body().toString());**
} catch (Exception e) {
mDisplayDetails.setText(e.getMessage());
}
mProgressBar.setVisibility(View.INVISIBLE);
}
#Override
public void onFailure(Throwable t) {
mDisplayDetails.setText(t.getMessage());
mProgressBar.setVisibility(View.INVISIBLE);
}
});
That's because it's already converted to an object by converter. To get the raw json, you need an interceptor on your Http Client. Thankfully you don't need to write your own class, Square already provide HttpLoggingInterceptor class for you.
Add this on your app-level gradle
compile 'com.squareup.okhttp3:logging-interceptor:3.5.0'
And use it in your OkHttpClient
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor).build();
Don't forget to change your HttpClient in Retrofit.
Retrofit retrofit = new Retrofit.Builder()
.client(client)
.baseUrl("https://yourapi.com/api/")
.build();
In the Log Cat you'll see the raw json response. Further information is available on Square's OkHttp github.
Caution!
Don't forget to remove Interceptors (or change Logging Level to NONE) in production! Otherwise people will be able to see your request and response on Log Cat.
You have to use "ResponseBody" from okhttp3 in your call. Then, get "response.body().string()" to get the JSONObject as your server gives to you.
It's a good way to catch errors if there are any errors parsing server response to your model object.
Simply use:
Log.i("RAW MESSAGE", response.raw().body().string());
Or:
Log.i("RAW MESSAGE", response.body().string());
Guess you want to see the raw response body for debugging purpose. There are two ways to do this.
Using okhttp3.logging.HttpLoggingInterceptor as #aldok mentioned.
If you want to check some properties of the response object, or convert the json(response body) to POJO manually, you may want to do it like this:
Don't use the addConverterFactory while initializing the retrofit client, comment this line.
retrofit = new Retrofit.Builder()
.client(client)
.baseUrl(BASE_URL)
//.addConverterFactory(GsonConverterFactory.create())
.build();
Then consuming the response like this:
Call<ResponseBody> topRatedResponseCall = apiService.getTopRatedMoves(API_KEY);
topRatedResponseCall.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
Log.d(LOG_TAG, response.body().string());
int code = response.code();
testText.setText(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
Hope this would help~
I use this to config my retrofit:
RestAdapter restAdapter = new RestAdapter.Builder()
//add headers to requests
.setRequestInterceptor(getAuthenticatedRequestInterceptor())
.setEndpoint(BASE_URL)
.setConverter(new GsonConverter(getGson()))
.build();
and The getAuthenticatedRequestInterceptor() method adds headers to request:
public AccountRequestInterceptor getAuthenticatedRequestInterceptor() {
AccountRequestInterceptor interceptor = new AccountRequestInterceptor();
Map<String, String> headers = new HashMap<>();
String accessToken = null;
try {
accessToken = TokenProvider.getInstance(mContext).getToken();
} catch (InterruptedException e) {
}
headers.put(HeadersContract.HEADER_AUTHONRIZATION, O_AUTH_AUTHENTICATION + accessToken);
interceptor.setHeader(headers);
return interceptor;
}
getToken() method is:
private synchronized string getToken() throws InterruptedException {
if (!isRefreshing()) {
//This is very important to call notify() on the same object that we call wait();
final TokenProvider myInstance = this;
setRefreshing(true);
MyApplication.getRestClient().getAccountService().getRefreshedToken(mLoginData.getRefreshToken())
.subscribe(new Observer<LoginResponse>() {
#Override
public void onCompleted() {
synchronized (myInstance) {
setRefreshing(false);
myInstance.notifyAll();
}
}
#Override
public void onError(Throwable e) {
synchronized (myInstance) {
setRefreshing(false);
myInstance.notifyAll();
}
}
#Override
public void onNext(LoginResponse loginResponse) {
synchronized (myInstance) {
mLoginData = loginResponse;
mAccountProvider.saveLoginData(loginResponse);
myInstance.notifyAll();
}
}
});
}
this.wait();
return mLoginData.getToken();
}
The TokenProvider.getInstance(mContext).getToken() has a wait() on main thread to get the response from an async method and i know that is a bad thing to do but i need this here to wait for the response to take the token from it and then return the token.how can i do this in a separate thread to avoid waiting on the main thread?
Note:
1 - that this is called before any request with retrofit.
2 - I read this and i know i can refresh token after a fail request, but for business reasons i want to avoid having an invalid token.
3 - I call MyApplication.getRestClient().getAccountService().login(loginRequest,callback...) in my Activity and before adding token everything happened in background thread. so I want to use my token and do not block the main thread.
UPDATE: I added the following Interceptor to my new OkHttp:
public class RequestTokenInterceptor implements Interceptor {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
Request newRequest;
try {
Log.d("addHeader", "Before");
String token = TokenProvider.getInstance(mContext).getToken();
if (token != null) {
newRequest = request.newBuilder()
.addHeader("Bearer", token)
.build();
} else {
// I want to cancel the request or raise an exception to catch it in onError method
// of retrofit callback.
}
} catch (InterruptedException e) {
Log.d("addHeader", "Error");
e.printStackTrace();
return chain.proceed(request);
}
Log.d("addHeader", "after");
return chain.proceed(newRequest);
}
}
Now how can i cancel the request or raise an exception to catch it in onError method of retrofit callback, if token is null?
It's a little bit strange issue but let me try to help you. :)
As you know you can refresh token after a failed request with retrofit using response interceptor.
Let's try to use interceptor before request.
public class RequestTokenInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// Here where we'll try to refresh token.
// with an retrofit call
// After we succeed we'll proceed our request
Response response = chain.proceed(request);
return response;
}
}
And when you're creating your api create a new HttpClient:
OkHttpClient client = new OkHttpClient();
client.interceptors().add(new RequestTokenInterceptor());
And add your http client to your adapter like below:
.setClient(new OkClient(client))
If this works, before every request you'll try to refresh token first and then will proceed your api request. So in ui there'll be no difference with your normal api calls.
Edit:
I'm editing my answer too. If you want to return an error in else case if token null, in else case you can create your custom response:
private Response(Builder builder) {
this.request = builder.request;
this.protocol = builder.protocol;
this.code = builder.code;
this.message = builder.message;
this.handshake = builder.handshake;
this.headers = builder.headers.build();
this.body = builder.body;
this.networkResponse = builder.networkResponse;
this.cacheResponse = builder.cacheResponse;
this.priorResponse = builder.priorResponse;
}
or simply you can return a null response. if you build your custom response and set your code not to 200 such as 401 or 400+ you'll receive that response in Retrofit's callbacks failure method. Than you can do what ever you want.
If you return null you'll get a RuntimeException i think and still you can catch response in your callback's failure method.
After you create your own response in else you can create your custom callback and catch your null response and transform your custom error how ever you want like below:
public abstract class DefaultRequestCallback<T> implements Callback<T> {
public abstract void failure(YourCustomException ex);
public abstract void success(T responseBean);
#Override
public void success(T baseResponseBean, Response response) {
if (response == null) {
// Here we catch null response and transform it to our custom Exception
failure(new YourCustomException());
}
} else {
success(baseResponseBean);
}
}
#Override
public void failure(RetrofitError error) {
// Here's your failure method.
// Also you can transform default retrofit errors to your customerrors
YourCustomException ex = new YourCustomException();
failure(ex);
}
}
This can help you i think.
Edit 2:
You can build a new Response like below. There's a builder pattern in Retrofit's Response class. You can check it from there.
Response response = new Response.Builder().setCode(401).setMessage("Error Message").build();
You could make all long actions in AsyncTask doInBackground method, while in onPre- and onPostExecute you could show/hide some progress bars when user is waiting
Ok, I think if you are calling your getAuthenticatedRequestInterceptor() on the main thread and which in turns call getInstance(),in which i feel you would be creating an object of Type TokenProvider hence when you create this object in the main thread your object.wait() runs on main thread hence to run this on a background thread probably modify your getAuthenticatedRequestInterceptor() method to execute the following lines in a new thread.
try {
accessToken = TokenProvider.getInstance(mContext).getToken();
} catch (InterruptedException e) {
}
headers.put(HeadersContract.HEADER_AUTHONRIZATION, O_AUTH_AUTHENTICATION + accessToken);
interceptor.setHeader(headers);
return interceptor;
but this will have problems for notifying your RestAdapter as the main thread will proceed executing, hence i would suggest
you call getAuthenticatedRequestInterceptor() method first in a new thread and then notify your main thread to build your RestAdapter.This will free your main thread but with the strategy you are employing you will have to wait until you receive the token to make any calls.
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/