Retrofit2 - check response code globally - android

I'm using Retrofit2 to make requests to server.
The problem is: sometimes the server will return code 401 for every request from an user. If the user get this code, he should be immediately kicked out from the app (logged out and not be able to do anything before re-login).
So for every request that being sent to the server, I want to check if the server response this code. It's not beautiful writing this check in all the request calls, so I want to write this check only one and it will perform every time user makes request!

Retrofit (current release) needs an HTTP client to make requests. OkHttp library by same developer comes bundled with Retrofit as default client. OkHttp supports adding Interceptor's to the client which can intercept request execution.
For Example:
import android.util.Log;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
public class ErrorInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
// before request
Request request = chain.request();
// execute request
Response response = chain.proceed(request);
// after request
// inspect status codes of unsuccessful responses
switch (response.code()){
case 401:
// do something else
Log.e("TEST","Unauthorized error for: " +request.url());
// perhaps throw a custom exception ?
throw new IOException("Unauthorized !!");
}
return response;
}
}
To use it, include it in OkHttpClient that Retrofit instance uses:
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new ErrorInterceptor())
.build();
Retrofit retrofit = new Retrofit.Builder()
.client(client)
.baseUrl("/")
.build();
So, you can implement an Interceptor for each "global logic" or "cross-cutting concern" and add them all in a sequence to Retrofit.

If you need check "401" code there is special object in OkHttp for it: Authenticator (Recipes in OkHttp). For example:
public class RefreshTokenAuthenticator implements Authenticator {
#Override
public Request authenticate(Route route, Response response) throws IOException {
// You get here, if response code was 401.
// Then you can somehow change your request or data in your app in this method and resend your request.
Request request = response.request();
HttpUrl url = request.url().newBuilder()
.setQueryParameter("access_token", "new_access_token_may_be")
.build();
request = request.newBuilder()
.url(url)
.build();
return request;
}
}

Related

Android retrofit repeat request after token expired?

I'm using Retrofit to commuicate to my server.
I have to call the login endpoint first, then I got a cookie (auth. token), which I store automatically, and the following requests are being authorized.
This 'session' expires if there is no request for 1 hour.
How should I do the automatic re-authenticating?
Obviously it's not a good idea, that I ping the login endpont with the username/password before every "real" request, to be sure that my client has not expired yet.
I tried adding interceptor to the okHttpClient, and check whether my "real" response (not the login) gets back code 401. In that case I should call the login endpoint, and after that, I have to repeat the "real" call.
How can I "save" the request, call login, and after that repeat the first request when I'm authenticated again?
private static Retrofit retrofit;
public static Retrofit getClient( final Context context ) {
if ( retrofit == null ) {
ClearableCookieJar cookieJar =
new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(context));
final OkHttpClient okHttpClient =
new OkHttpClient.Builder()
.followSslRedirects(false)
.followRedirects(false)
.cookieJar(cookieJar)
.addInterceptor(chain -> {
Request request = chain.request();
Response response = chain.proceed(request);
if ( response.code() == 401 ) {
if ( !response.message().contains("Incorrect") ) {
// THIS IS THE CASE, when I try to call an endpoint with expired token
// I need to call login again, and then repeat this failed request
}
}
return response;
})
.build();
retrofit = new Retrofit.Builder()
.baseUrl(URL)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
}
return retrofit;
}
Obviously I can check manually every request's response code, and if it's 401, I call login, and when it's done, I call my request again. But I'm hoping that there is an easier (built-in solution with interceptors, so I don't have to implement this logic everywhere)
After login response, save the token, then carry this token to commuicate with server, so that user don't need to login again.
When app gets a response like "401:expired token" during any request, just delete the saved token and all the user data and show a dialog let user jump to LoginActivity. Then repeat.

Android Retrofit Interceptor add body param for all calls

Is there any way to edit the body of a network call for adding a default attribute used in the 95% of the calls?
I've seen that a query parameter is pretty easy to add (link)
But, I have not seen it for a Body.
My problem is that I'm working with an old API that asks me to send in each request the token. So I need to add this line in most of the classes.
#SerializedName("token") val token: String
Any ideas?
You should use httpInterceptor to solve this problem if you send in header
final OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request()
.newBuilder()
// add token key on request header
// key will be using access token
.addHeader("token", yourToken)
.build();
return chain.proceed(request);
}
});
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build();
Edit : Im sorry, I've realized now you ask about sending in body.
I think it can be possible with old way(without Gson, Moshi etc). It is really more annoying than adding to every request.

Retrofit 2 - Is it possible to get the size of a JSON that I'm sending and receiving?

I need to build a traffic monitor on my Android app, and I need to have stored the size of all json that I'm sending and receiving through retrofit. Using log I can see the actual size of it, but I haven't find a way to get this information so I could save it. I can't get the response.raw either since it's already been parsed to my classes. Is there any way to achieve that?
EDIT: Marked vadkou answer as the best one.
Instead of creating a new interceptor, I passed the lamda expression:
httpClient.addInterceptor( chain -> {
okhttp3.Request request = chain.request();
okhttp3.Response response = chain.proceed(request);
if(request.body()!=null) {
long requestLength = request.body().contentLength();
Log.e("SERVICE GENERATOR", " CONTENT LENGTH" + requestLength);
}
long responseLength = response.body().contentLength();
Log.e("SERVICE GENERATOR", " RESPONSE LENGTH" + responseLength);
return response;
});
Retrofit 2 uses OkHttp internally, and you could configure OkHttp without having to resort to getting raw HTTP response as in Vaiden's answer by adding a custom Interceptor while building an adapter as follows:
private Retrofit createRetrofit() {
return new Retrofit.Builder()
.baseUrl(END_POINT)
// .addConverterFactory(...)
// .addCallAdapterFactory(...)
.client(createClient())
.build();
}
private OkHttpClient createClient() {
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
okHttpClientBuilder.addInterceptor(createYourInterceptor());
return okHttpClientBuilder.build();
}
The Interceptor interface among other things allows you to access request body for every request you make.
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// do what you want with request.body().contentLength();
return chain.proceed(request);
}
For this you need to create custom interecptor
please reffere below example
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
public class CustomIntercepter implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();///
Response response = chain.proceed(request);
// for request size
long requestLength = request.body().contentLength();
// for response size
long responseLength = response.body().contentLength();
return response;
}
}
`
Now Create Retrofit object
OkHttpClient provideOkHttpClient(CustomIntercepter customIntercepter) {
OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder();
okHttpClient.cache(cache);
okHttpClient.addInterceptor(customIntercepter);
return okHttpClient.build();
}
Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(SERVER_URL)
.client(okHttpClient)
.build();
return retrofit;
}
You should try accessing the raw HTTP response (Get raw HTTP response with Retrofit):
You begin with a Response object.
This object has a .raw() method that returns the actual HTTP layer's reponse,
in the form of an okhttp3.Response object. Calling .body() would give you a ResponseBody object, which encapsulates the raw response.
You can get the length of the response by calling .contentLength().

Freso: How can I set the OK Http client?

So some images I request require an authentication header to be added
I am using Retrofit 2.0 which has this OkHttp client with a interceptor to add the user token to the header to every request
okHttpClient.interceptors().add(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request(); //Current Request
Request requestWithToken = null; //The request with the access token which we will use if we have one instead of the original
requestWithToken = originalRequest.newBuilder().addHeader(Constants.UrlParamConstants.HEADER_AUTHORIZATION,String.format(Constants.UrlParamConstants.HEADER_AUTHORIZATION_VALUE, MyApplication.getInstance().getUser().getApiToken())).build();
Response response = chain.proceed((requestWithToken != null ? requestWithToken : originalRequest)); //proceed with the request and get the response
return response;
}
});
I would like to know how can I set the same okHttp client instance for Fresco library.
I am aware that you need to add this dependency to use OkHttp with Fresco but how about setting the client?
compile "com.facebook.fresco:imagepipeline-okhttp:0.8.0+"
At the end of the day I just need to set authentication header for an image request
thanks for reading
http://frescolib.org/docs/using-other-network-layers.html
Context context;
OkHttpClient okHttpClient; // build on your own
ImagePipelineConfig config = OkHttpImagePipelineConfigFactory
.newBuilder(context, okHttpClient)
. // other setters
. // setNetworkFetcher is already called for you
.build();
Fresco.initialize(context, config);

Make a synchronous Retrofit call from inside an OkHttp Interceptor

I am trying to automatically refresh an auth token if it is expired. I am using the new Interceptor class that was introduced in OkHttp 2.2. In the intercept method I am trying the original request with chain.proceed(request), checking the response code, and if the token is expired I am making a call to a separate Retrofit service, synchronously, to obtain a new token.
The strange thing is, no code past the synchronous call seems to run. If I try debugging with a breakpoint on the synchronous call's line, then do a step-over, I am stopped in Dispatcher.java at :
if (!executedCalls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
Any idea as to what I might be doing wrong here? I could probably just craft a new request by hand, but I am just kind of curious why a Retrofit call doesn't seem to work here.
My Interceptor:
public class ReAuthInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// try the request
Response response = chain.proceed(request);
// if we receive a "401 - Not Authorized" then refresh the auth token and try again
if (response.code() == 401) {
// get a new auth token and store it
UserToken userToken = MobileClient.getOkraService().login(AuthUtil.getUserToken(MSWorksApplication.getContext()));
AuthUtil.authenticate(MSWorksApplication.getContext(), userToken);
Log.d("TEST", "TEST TEST TEST");
// use the original request with the new auth token
Request newRequest = request.newBuilder().header("Authorization", AuthUtil.getAuthToken(MSWorksApplication.getContext())).build();
return chain.proceed(newRequest);
}
else {
// the auth token is still good
return response;
}
}
}
I had this problem - if you're getting an HTTP response code that would cause Retrofit to call failure() instead of success() an exception will be thrown.
If you wrap
UserToken userToken = MobileClient.getOkraService().login(AuthUtil.getUserToken(MSWorksApplication.getContext()));
in a try/catch(RetrofitError e) you'll be able to execute code after a failure, however I've found this to be quite cumbersome and am still in the process of finding a nicer solution.

Categories

Resources