Is there a nice way to implement "blocking" request interceptor?
The main idea is that all requests should be intercepted and added additional header - token.
If token does not exist yet it should be retrieved first, then added to that request and cached for future used. token is retrieved via API call.
I've tried to do synchronous request, but, that produces android.os.NetworkOnMainThreadException. And implementing with in_progress flags it doesn't look nice.
You can already do the 'intercept' part of this using RequestInterceptor. Just use RestAdapter.Builder.setRequestInterceptor().
It's a better idea to retrieve the token from the API outside the RequestInterceptor though, as it's not meant to do that. After that first call, you can just add the token anywhere you want in your requests inside RequestInterceptor.intercept().
Something like this:
Builder builder = new RestAdapter.Builder()
//Set Endpoint URL, Retrofit class... etc
.setRequestInterceptor(new RequestInterceptor() {
#Override
public void intercept(RequestFacade request) {
String authToken = getAuthToken(); //Not included here, retrieve the token.
request.addHeader("Authorization", "Bearer " + authToken);
}
);
Well, you have already implemented your 'blocking' interceptor, your problem is android doesn't let you block the main thread with network calls.
You should probably wrap your retrofit calls in a service class that calls, asynchronously, to your getToken method, and makes the 'main' request only if and when that first one completes succesfully.
As of OkHTTP 2.2, you can now add interceptors that run on the network thread:
https://github.com/square/okhttp/wiki/Interceptors
An example interceptor for adding an auth token might be like this;
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// Get your auth token by going out over the network..
// add authorization header, defaulting to the original request.
Request authedRequest = request;
if (!TextUtils.isEmpty(authToken)) {
authedRequest = request.newBuilder().addHeader("Auth", authToken).build();
}
return chain.proceed(authedRequest);
}
Related
I have a login endpoint by which I receive an authToken and a refreshToken. The first one expires in an hour, so I should use the second one to refresh it and continue the application flow.
Now my app is filled with retrofit calls all over the place and I potentially could get a 401 at any given moment, so how can I make it so every time I get a 401 a refresh token is issued, and then the original request retried?
This is my refresh signature:
#POST("/auth/actions/refresh")
fun refreshToken(#Body tokenRefresh: TokenRefresh): Single<LoginResponse>
I thought about making a base repository class with a method withAuth() that takes any Observable/Single/Flowable and then applies this logic but I cannot find a way to implement it.
Saw a bunch of implementations but none of them match my needs... can anyone push me in the right direction?
This is the closest I've found, however there seems to be some errors on the flatmapping
I just came across similar requirement and came up with following solution. It's pretty simple, it just makes one attempt to call REST endpoint and if that call fails with HTTP 401 it reauthenticates and repeats the call again. Otherwise it just emits the original error.
fun <T> Single<T>.withAuth() = retryWhen { errors ->
var firstAttempt = true
errors.flatMapSingle { error ->
if (firstAttempt && error is HttpException && error.code() == 401) {
firstAttempt = false
reauthenticate()
} else {
Single.error(it)
}
}
}
where the reauthentication function has the following signature:
fun reauthenticate(): Single<AnyAuthResponse>
Please note that the concrete exception type might depend on HTTP implementation you actually use, so you may want to update the condition to detect HTTP 401 response, but the code should give you an overall picture of how to solve your problem.
I think you can do this without modifying all calls. Add an Authenticator to your Retrofit
Refreshing OAuth token using Retrofit without modifying all calls
You can use Interceptor to intercept each request and check whether it returns 401 - UnAuthorised Access and iff then refresh the token and replay the current API request.
public final class SessionInterceptor implements Interceptor {
// gets intercept
#Override public Response intercept(#NonNull final Chain chain) throws IOException {
final Request request = chain.request();
final Response response = chain.proceed(request);
final ResponseBody responseBody = response.body();
if (response.code() == 401) {
synchronized (this) {
// Refresh your token
// Update your authToken + Refreshed token
final retrofit2.Response response = refreshToken();
}
}
// Replay the original request
// Perform request, here original request will be executed
final Request original = chain.request();
final Request.Builder builder = original.newBuilder();
// Set your new refreshed token
if (accessToken.isSet()) {
builder.header(AUTHORIZATION, String.format(BEARER,
accessToken.get()));
}
final Request request = builder.method(original.method(), original.body()).build();
return chain.proceed(request);
}
}
I am new to android programming and Retrofit , I am making a sample app where i have to make two parallel network calls using access token.
The problem comes when access token is expired and return 401 status code , if I see 401 HTTP status code I have to make a call to refresh token with this access token , but problem with parallel calls is that it leads to race condition for refreshing the refresh token , is there any best practice of way to avoid such situation and how to intelligently refresh the token without any conflict.
OkHttp will automatically ask the Authenticator for credentials when a response is 401 Not Authorised retrying last failed request with them.
public class TokenAuthenticator implements Authenticator {
#Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
// Refresh your access_token using a synchronous api request
newAccessToken = service.refreshToken();
// Add new header to rejected request and retry it
return response.request().newBuilder()
.header(AUTHORIZATION, newAccessToken)
.build();
}
#Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
// Null indicates no attempt to authenticate.
return null;
}
Attach an Authenticator to an OkHttpClient the same way you do with Interceptors
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setAuthenticator(authAuthenticator);
Use this client when creating your Retrofit RestAdapter
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(ENDPOINT)
.setClient(new OkClient(okHttpClient))
.build();
return restAdapter.create(API.class);
Check this: Fore more details visit this link
Try to make a queue for the refresh token operations like:
class TokenProcessor {
private List<Listener> queue = new List<Listener>();
private final Object synch = new Object();
private State state = State.None;
private String token;
private long tokenExpirationDate;
public void getNewToken(Listener listener){
synchronized(synch) {
// check token expiration date
if (isTokenValid()){
listener.onSuccess(token);
return;
}
queue.add(listener);
if (state != State.Working) {
sendRefreshTokenRequest();
}
}
}
private void sendRefreshTokenRequest(){
// get token from your API using Retrofit
// on the response call onRefreshTokenLoaded() method with the token and expiration date
}
private void onRefreshTokenLoaded(String token, long expirationDate){
synchronized(synch){
this.token = token;
this.tokenExpirationDate = expirationDate;
for(Listener listener : queue){
try {
listener.onTokenRefreshed(token);
} catch (Throwable){}
}
queue.clear();
}
}
}
This is an example code, how it can be implemented.
To avoid race conditions, you could synchronize refresh token code using ReentrantLock. For instance, if request A and request B try to refresh token at the same time, since code is synchronized, refresh A gets to actually refresh the token. Once it completes, request B will run refreshToken() and there's should be some logic that tells request B that token has already been refreshed. An example could be storing timestamp of when the token refresh happens then check if token has been refreshed last 10 seconds.
val lock = ReentrantLock(true)
fun refreshToken(): Boolean {
lock.lock()
if (token has been refreshed in last 10 seconds): return true
api.refresh()
lock.unlock()
}
If you don't want to use last 10 seconds logic, here's a different approach. Whenever you refresh token, backend returns {accessToken, expiration-timestamp}. Now, request A saves this token and expiration in disk. Request B will just need to check to make sure token is not expired using the timestamp. If request B gets 401 and token has not expired, it means request A has refreshed the token. Sample code:
val lock = ReentrantLock(true)
fun refreshToken(): Boolean {
lock.lock()
if (token has not expired): return true
api.refresh()
lock.unlock()
}
Otherwise, you probably have to create a queue for refresh token operations as mentioned above.
I'm using an api that implements throttling. One of the limits is 1 request/second. ugh.
I have the following scenario which hits the limit right away.
Check the status of the api with api/status
if the api is up, get a users subscriptions
load a page from the list of subscriptions
Is there anything I can plug into retrofit that can queue each network request to only run at least 1000ms after the last? I am using/learning rxjava, can debounce be of any use here?
You can throttle your observable.
Observable<String> text = ...
text.throttleLast(1, SECONDS)
.flatMap(retrofitApiCall())
.subscribe(result -> System.out.println("result: " + result));
Another solution is to set a dispatcher in your okhttp builder, and add an interceptor that sleeps for one second. This may not be the most elegant solution and kills some of the benefits of using async because it limits you to one thread at a time.
OkHttpClient.Builder builder = new OkHttpClient.Builder();
Dispatcher dispatcher = new Dispatcher();
dispatcher.setMaxRequests(1);
Interceptor interceptor = new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
SystemClock.sleep(1000);
return chain.proceed(chain.request());
}
};
builder.addNetworkInterceptor(interceptor);
builder.dispatcher(dispatcher);
builder.build();
An interceptor (from OkHttpClient) combined with a RateLimiter (from Guava) is a good solution to avoid HTTP 429 error code.
Let's suppose we want a limit of 3 calls per second:
import java.io.IOException;
import com.google.common.util.concurrent.RateLimiter;
import okhttp3.Interceptor;
import okhttp3.Response;
public class RateLimitInterceptor implements Interceptor {
private RateLimiter limiter = RateLimiter.create(3);
#Override
public Response intercept(Chain chain) throws IOException {
limiter.acquire(1);
return chain.proceed(chain.request());
}
}
Actually, you can use already made OkHttp Delay Interceptor library. Just call .addInterceptor(DelayInterceptor(Long, TimeUnit)) method on your's OkHttp client builder object.
I am using HttpUrlConnection for doing requests to my mysql db using webservices. With HttpUrlConnection I can execute all my requests in background so the main thread don't get overloaded and start skipping frames.
With okHttp how does this is achieved? How do I make a request with it and print a response using JSON? Is it better than httpUrlConnection?
P.S I do not know anything about okHttp, I will be grateful if you are explicit with your examples.
With okHttp how does this is achieved?
Typically, you let it handle the background thread for you, using enqueue() for asynchronous operation:
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
client.newCall(request).enqueue(new Callback() {
#Override public void onFailure(Call call, IOException e) {
// handle the error
}
#Override public void onResponse(Call call, Response response) throws IOException {
// use the result
}
});
}
(sightly simplified from the OkHttp docs)
Or, if you already have a background thread, you can use execute() instead of enqueue() for synchronous operation.
You might wish to review the other examples on the OkHttp recipes page, plus the OkHttp Web page, plus the OkHttp wiki, to get a better sense of how it compares with what you are used to.
I am making an android app using Retrofit 2. My REST Api are all written in Liferay. Now in Liferay, what I have seen is, to access the web services we need to authenticate first. So i have authenticated like this
http://test:q1w2e3r4#192.168.0.110:8080/liferay-portlet/api/secure/jsonws/
Liferay has its own user authentication method which we have overridden.I checked the Web service call from Postman its working fine.
URL:http://test:q1w2e3r4#192.168.0.110:8080/liferay-portlet/api/secure/jsonws/customuserauthentication/authenticate-by-user-name
form-encoded values
companyId:10154
screenName:xyz
password:xyz
active:true
If i put this in the postman, it fetches the json response properly.
Now when i call the same from my android code i get a response "Unauthorized".
My Retrofit service
public interface LoginApi {
#FormUrlEncoded
#POST("/liferay-portlet/api/secure/jsonws/customuserauthentication/authenticate-by-user-name")
Call<User> login(#Field("companyId")long companyId,#Field("screenName")String screenName,#Field("password")String password,#Field("active")boolean active);
}
My RestApiManager Class(This class is used to call the service interface and create the retrofit builder)
public class RestApiManager {
private LoginApi loginApi;
public LoginApi login() {
if (loginApi==null) {
GsonBuilder gson=new GsonBuilder();
gson.registerTypeAdapter(String.class, new StringDeserializer());
Retrofit retrofit=new Retrofit.Builder()
.baseUrl("http://test:q1w2e3r4#192.168.0.110:8080")
.addConverterFactory(GsonConverterFactory.create())
.build();
loginApi=retrofit.create(LoginApi.class);
}
return loginApi;
}
A call to the RestApiManager
Call<User> callUser=restApiManager.login().login(loginData.getCompanyId(),loginData.getScreenName(),loginData.getPassword(),loginData.isActive());
callUser.enqueue(new Callback<User>() {
#Override
public void onResponse(Response<User> response, Retrofit retrofit) {
Log.d("Login","Login Response:"+response.body());
}
#Override
public void onFailure(Throwable t) {
Log.d("Login","Login Response:"+t.getMessage());
}
});
It looks like perhaps your request should have a JSON body instead of a POST variables? You are calling a JSON webservice and your example parameters look more JSON than POST. If so, then you can encapsulate your parameters in an object --
public class User {
int companyId;
String screenName;
String password;
boolean active;
User(int companyId, String screenName, String password, boolean active) {
this.companyId = companyId;
this.screenName = screenName;
this.password = password;
this.active = active;
}
Your interface would be --
public interface LoginApi {
#POST("/liferay-portlet/api/secure/jsonws/customuserauthentication/authenticate-by-user-name")
Call<User> login(#Body User user);
}
and construct your call as --
User user = new User(loginData.getCompanyId(),loginData.getScreenName(),loginData.getPassword(),loginData.isActive());
Call<User> callUser = restApiManager.login().login(user);
The session management in cross-platform does not work the way it works on browser. Postman is a web client that works on browser platform.
1.One solution is to maintain cookies on the device.
Check this answer manage sessions with Android Application.
2.The other solution would be to use Json Web Token based auth.
I tried to run your demo code in my local PC, but no lucky as your server is in your local area network, about this question if you are using cookie or session authorize in you server side, I would suggestion you try to setCookieHandler as following, the PersistentCookieStore.java you can find here
private PersistentCookieStore cookieStore= new PersistentCookieStore(JApplication.getInstance().getApplicationContext());
CookieManager cookieManager = (new CookieManager(
cookieStore,
CookiePolicy.ACCEPT_ALL));
okHttpClient.setCookieHandler(cookieManager);
OkClient okClient = new OkClient(okHttpClient);
RestAdapter restAdapter = new RestAdapter.Builder()
.setRequestInterceptor(new RequestInterceptor() {
#Override
public void intercept(RequestFacade request) {
request.addHeader(Constant.Header_UserAgent, getUserAgent());
}
})
.setClient(okClient)
.setEndpoint(URL)
.setErrorHandler(new HttpErrorHandler())
.setConverter(new GsonConverter(gson))
.build();
It may be difficult to us to help you without knowing exactly which request your Retrofit Api is building.
So I recommend you to configure a Logging Interceptor to get in touch with what is really happening then if you still don't know what is going on you could come back to us with more information.
Check this answer to discover how to configure LoggingInterceptor with Retrofit 2:
Logging with Retrofit 2
Hope that it helps.
Best regards.
I tried all the solutions given here, but unfortunately nothing worked.The only solution i found to call the lifer service from within android is to use lifer mobile sdk.It already has methods and services to call liferay services. You can also call upon your custom services.More info
Liferay Mobile SDK