In a not so exotic situation I need access to the original request when I handle the response in the responseBodyConverter. For example, right now I need the original url -- e.g. I want to adjust relative urls in the response HTML. And perhaps I will need some request header, like a cookie.
But I don't know how to get the request data.
Here is what I have:
HttpLoggingInterceptor hlog = ses.http_logger(servername);
if (hlog != null) {
builder.addInterceptor(hlog);
}
OkHttpClient okhttpClient = builder
.build();
return new Retrofit.Builder()
.baseUrl(base_url)
.client(okhttpClient)
.addConverterFactory(
new Converter.Factory() {
#Override
public Converter<ResponseBody, ?> responseBodyConverter(final Type type, final Annotation[] annotations, final Retrofit retrofit) {
return responseBody -> {
BufferedSource buffer = Okio.buffer(responseBody.source());
String html = buffer.readUtf8();
System.out.println(html);
};
}
})
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.build()
;
I found this solution https://github.com/square/retrofit/issues/2267/
But I can not understand what it does or whether it is too complex or any side effects.
Can I get access to the request data in Retrofit2 while processing the response in responseBodyConverter and how?
Edit: the above works as expected, but a simpler way is preferred.
I really need help!
I have a server that sends me predicting time. I need to send request to the server every 10 minutes(even if the app closed) and ask the current time. if the time changed I need to make a notification to the client.
the stop trigger is if the predicting time is now.
so - I have ClientActivity and I have the code to communicate the server.
How can I do that?
Thanks a lot!
public interface APIClient {
#POST("/api/post_some_data")
Call<PostResponse> getPostRequest(#Body PostRequest body);
}
public class NetworkClient {
public static final String BASE_URL = "http://*****:8080";
public static Retrofit retrofit;
/*
This public static method will return Retrofit client
anywhere in the appplication
*/
public static Retrofit getRetrofitClient() {
//If condition to ensure we don't create multiple retrofit instances in a single application
if (retrofit == null) {
//Defining the Retrofit using Builder
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL) //This is the only mandatory call on Builder object.
.addConverterFactory(GsonConverterFactory.create()) // Convertor library used to convert response into POJO
.build();
}
return retrofit;
}
}
I am trying to send the converted file (Base64 - String) as a parameter in POST, the file is about 8 MB, but sending takes about 4 minutes. Is there a way to speed up?
Interface:
#FormUrlEncoded
#POST("upload")
Call<Upload> upload(#Field("CONTENT") String content);
Retrofit instance:
public class RetrofitClientInstance {
private static Retrofit retrofit;
private static OkHttpClient client;
public static Retrofit getRetrofitInstance(String url) {
if (retrofit == null && !url.isEmpty()) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.build();
retrofit = new retrofit2.Retrofit.Builder()
.baseUrl(url)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}}
Call:
private void upload(){
Api api = RetrofitClientInstance.getRetrofitInstance(SharedUtils.SERVER_URL).create(Api.class);
Call<Upload> request = api.upload(getBase64FromFile());
request.enqueue(new Callback<Upload>() {
#Override
public void onResponse(Call<Upload> call, Response<Upload> response) {
}
#Override
public void onFailure(Call<Upload> call, Throwable t) {
}
});
}
Try to compress your file or image before uploading, because it would take so much time to make it done
First of all, you are using enqueue() method of retrofit which is an asynchronous way of executing the code and you have registered callbacks to these methods on successful execution you will receive the call inside onResponse() method but on failure, you will get control inside the onFailure() method.
This will spawn a thread of execution from the daemon thread which creates another thread of execution you may never know when will this thread gets executed based on OS priority.
Use execute() method to execute this in a synchronous manner and then check the response time it would give a proper result.
I have an HTTP request inside of a method and i'm trying to return true/false based on the response from the request. im not sure what im doing wrong, I thought it was pretty straight forward. in the test scenario the condition inside of the onResponse call returns true. not sure whats getting lost in translation.
Also, in the final condition, the "message" variable is purple, in android studio, im not sure what this indicates. I think its related to my issue. suggestions?
public class DeleteButtonChecker {
public String message = new String();
public Boolean doCheck(int userID, int postID){
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
Gson gson = new GsonBuilder()
.setLenient()
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
RequestInterface requestInterface = retrofit.create(RequestInterface.class);
Call<ServerResponse> response = requestInterface.check(postID, userID);
response.enqueue(new Callback<ServerResponse>() {
#Override
public void onResponse(Call<ServerResponse> call, Response<ServerResponse> response) {
ServerResponse resp = response.body();
if(resp.getResult().equals(Constants.SUCCESS)){
message = "true";
} else {
message = "false";
}
}
#Override
public void onFailure(Call<ServerResponse> call, Throwable t) {
}
});
if(message.equals("true")) {
return true;
} else{
return false;
}
}
}
The response is handled asynchronously. On the line:
response.enqueue(new Callback<ServerResponse>() {
...
You are simply giving a callback to be executed once the response is returned. However the method is not blocked and continues to the next statement - which is the:
if(message.equals("true")) {
Which can be translated to:
if("".equals("true")) {
An asynchronous request cannot be treated as a synchronous call, that is given your conditions.
If it was for me I would use RxJava 2 Single with Retrofit to return a usable result from the request.
Check this out: https://github.com/square/retrofit/tree/master/retrofit-adapters/rxjava2
And make sure to learn some RxJava which can help you on the road
I have a ViewPager and three webservice calls are made when ViewPager is loaded simultaneously.
When first one returns 401, Authenticator is called and I refresh the token inside Authenticator, but remaining 2 requests are already sent to the server with old refresh token and fails with 498 which is captured in Interceptor and app is logged out.
This is not the ideal behaviour I would expect. I would like to keep the 2nd and 3rd request in the queue and when the token is refreshed, retry the queued request.
Currently, I have a variable to indicate if token refresh is ongoing in Authenticator, in that case, I cancel all subsequent request in the Interceptor and user has to manually refresh the page or I can logout the user and force user to login.
What is a good solution or architecture for the above problem using okhttp 3.x for Android?
EDIT: The problem I want to solve is in general and I would not like to sequence my calls. i.e. wait for one call to finish and refresh the token and then only send rest of the request on the activity and fragment level.
Code was requested. This is a standard code for Authenticator:
public class CustomAuthenticator implements Authenticator {
#Inject AccountManager accountManager;
#Inject #AccountType String accountType;
#Inject #AuthTokenType String authTokenType;
#Inject
public ApiAuthenticator(#ForApplication Context context) {
}
#Override
public Request authenticate(Route route, Response response) throws IOException {
// Invaidate authToken
String accessToken = accountManager.peekAuthToken(account, authTokenType);
if (accessToken != null) {
accountManager.invalidateAuthToken(accountType, accessToken);
}
try {
// Get new refresh token. This invokes custom AccountAuthenticator which makes a call to get new refresh token.
accessToken = accountManager.blockingGetAuthToken(account, authTokenType, false);
if (accessToken != null) {
Request.Builder requestBuilder = response.request().newBuilder();
// Add headers with new refreshToken
return requestBuilder.build();
} catch (Throwable t) {
Timber.e(t, t.getLocalizedMessage());
}
}
return null;
}
}
Some questions similar to this:
OkHttp and Retrofit, refresh token with concurrent requests
You can do this:
Add those as data members:
// these two static variables serve for the pattern to refresh a token
private final static ConditionVariable LOCK = new ConditionVariable(true);
private static final AtomicBoolean mIsRefreshing = new AtomicBoolean(false);
and then on the intercept method:
#Override
public Response intercept(#NonNull Chain chain) throws IOException {
Request request = chain.request();
// 1. sign this request
....
// 2. proceed with the request
Response response = chain.proceed(request);
// 3. check the response: have we got a 401?
if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
if (!TextUtils.isEmpty(token)) {
/*
* Because we send out multiple HTTP requests in parallel, they might all list a 401 at the same time.
* Only one of them should refresh the token, because otherwise we'd refresh the same token multiple times
* and that is bad. Therefore we have these two static objects, a ConditionVariable and a boolean. The
* first thread that gets here closes the ConditionVariable and changes the boolean flag.
*/
if (mIsRefreshing.compareAndSet(false, true)) {
LOCK.close();
/* we're the first here. let's refresh this token.
* it looks like our token isn't valid anymore.
* REFRESH the actual token here
*/
LOCK.open();
mIsRefreshing.set(false);
} else {
// Another thread is refreshing the token for us, let's wait for it.
boolean conditionOpened = LOCK.block(REFRESH_WAIT_TIMEOUT);
// If the next check is false, it means that the timeout expired, that is - the refresh
// stuff has failed.
if (conditionOpened) {
// another thread has refreshed this for us! thanks!
// sign the request with the new token and proceed
// return the outcome of the newly signed request
response = chain.proceed(newRequest);
}
}
}
}
// check if still unauthorized (i.e. refresh failed)
if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
... // clean your access token and prompt for request again.
}
// returning the response to the original request
return response;
}
In this way you will only send 1 request to refresh the token and then for every other you will have the refreshed token.
It is important to note, that accountManager.blockingGetAuthToken (or the non-blocking version) could still be called somewhere else, other than the interceptor. Hence the correct place to prevent this issue from happening would be within the authenticator.
We want to make sure that the first thread that needs an access token will retrieve it, and possible other threads should just register for a callback to be invoked when the first thread finished retrieving the token.
The good news is, that AbstractAccountAuthenticator already has a way of delivering asynchronous results, namely AccountAuthenticatorResponse, on which you can call onResult or onError.
The following sample consists of 3 blocks.
The first one is about making sure that only one thread fetches the access token while other threads just register their response for a callback.
The second part is just a dummy empty result bundle. Here, you would load your token, possibly refresh it, etc.
The third part is what you do once you have your result (or error). You have to make sure to call the response for every other thread that might have registered.
boolean fetchingToken;
List<AccountAuthenticatorResponse> queue = null;
#Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
synchronized (this) {
if (fetchingToken) {
// another thread is already working on it, register for callback
List<AccountAuthenticatorResponse> q = queue;
if (q == null) {
q = new ArrayList<>();
queue = q;
}
q.add(response);
// we return null, the result will be sent with the `response`
return null;
}
// we have to fetch the token, and return the result other threads
fetchingToken = true;
}
// load access token, refresh with refresh token, whatever
// ... todo ...
Bundle result = Bundle.EMPTY;
// loop to make sure we don't drop any responses
for ( ; ; ) {
List<AccountAuthenticatorResponse> q;
synchronized (this) {
// get list with responses waiting for result
q = queue;
if (q == null) {
fetchingToken = false;
// we're done, nobody is waiting for a response, return
return null;
}
queue = null;
}
// inform other threads about the result
for (AccountAuthenticatorResponse r : q) {
r.onResult(result); // return result
}
// repeat for the case another thread registered for callback
// while we were busy calling others
}
}
Just make sure to return null on all paths when using the response.
You could obviously use other means to synchronize those code blocks, like atomics as shown by #matrix in another response. I made use of synchronized, because I believe this to be the easiest to grasp implementation, since this is a great question and everyone should be doing this ;)
The above sample is an adapted version of an emitter loop described here, where it goes into great detail about concurrency. This blog is a great source if you're interested in how RxJava works under the hood.
You can try with this application level interceptor
private class HttpInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//Build new request
Request.Builder builder = request.newBuilder();
builder.header("Accept", "application/json"); //if necessary, say to consume JSON
String token = settings.getAccessToken(); //save token of this request for future
setAuthHeader(builder, token); //write current token to request
request = builder.build(); //overwrite old request
Response response = chain.proceed(request); //perform request, here original request will be executed
if (response.code() == 401) { //if unauthorized
synchronized (httpClient) { //perform all 401 in sync blocks, to avoid multiply token updates
String currentToken = settings.getAccessToken(); //get currently stored token
if(currentToken != null && currentToken.equals(token)) { //compare current token with token that was stored before, if it was not updated - do update
int code = refreshToken() / 100; //refresh token
if(code != 2) { //if refresh token failed for some reason
if(code == 4) //only if response is 400, 500 might mean that token was not updated
logout(); //go to login screen
return response; //if token refresh failed - show error to user
}
}
if(settings.getAccessToken() != null) { //retry requires new auth token,
setAuthHeader(builder, settings.getAccessToken()); //set auth token to updated
request = builder.build();
return chain.proceed(request); //repeat request with new token
}
}
}
return response;
}
private void setAuthHeader(Request.Builder builder, String token) {
if (token != null) //Add Auth token to each request if authorized
builder.header("Authorization", String.format("Bearer %s", token));
}
private int refreshToken() {
//Refresh token, synchronously, save it, and return result code
//you might use retrofit here
}
private int logout() {
//logout your user
}
}
You can set interceptor like this to okHttp instance
Gson gson = new GsonBuilder().create();
OkHttpClient httpClient = new OkHttpClient();
httpClient.interceptors().add(new HttpInterceptor());
final RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(BuildConfig.REST_SERVICE_URL)
.setClient(new OkClient(httpClient))
.setConverter(new GsonConverter(gson))
.setLogLevel(RestAdapter.LogLevel.BASIC)
.build();
remoteService = restAdapter.create(RemoteService.class);
Hope this helps!!!!
I found the solution with authenticator, the id is the number of the request, only for identification. Comments are in Spanish
private final static Lock locks = new ReentrantLock();
httpClient.authenticator(new Authenticator() {
#Override
public Request authenticate(#NonNull Route route,#NonNull Response response) throws IOException {
Log.e("Error" , "Se encontro un 401 no autorizado y soy el numero : " + id);
//Obteniendo token de DB
SharedPreferences prefs = mContext.getSharedPreferences(
BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
String token_db = prefs.getString("refresh_token","");
//Comparando tokens
if(mToken.getRefreshToken().equals(token_db)){
locks.lock();
try{
//Obteniendo token de DB
prefs = mContext.getSharedPreferences(
BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
String token_db2 = prefs.getString("refresh_token","");
//Comparando tokens
if(mToken.getRefreshToken().equals(token_db2)){
//Refresh token
APIClient tokenClient = createService(APIClient.class);
Call<AccessToken> call = tokenClient.getRefreshAccessToken(API_OAUTH_CLIENTID,API_OAUTH_CLIENTSECRET, "refresh_token", mToken.getRefreshToken());
retrofit2.Response<AccessToken> res = call.execute();
AccessToken newToken = res.body();
// do we have an access token to refresh?
if(newToken!=null && res.isSuccessful()){
String refreshToken = newToken.getRefreshToken();
Log.e("Entra", "Token actualizado y soy el numero : " + id + " : " + refreshToken);
prefs = mContext.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
prefs.edit().putBoolean("log_in", true).apply();
prefs.edit().putString("access_token", newToken.getAccessToken()).apply();
prefs.edit().putString("refresh_token", refreshToken).apply();
prefs.edit().putString("token_type", newToken.getTokenType()).apply();
locks.unlock();
return response.request().newBuilder()
.header("Authorization", newToken.getTokenType() + " " + newToken.getAccessToken())
.build();
}else{
//Dirigir a login
Log.e("redirigir", "DIRIGIENDO LOGOUT");
locks.unlock();
return null;
}
}else{
//Ya se actualizo tokens
Log.e("Entra", "El token se actualizo anteriormente, y soy el no : " + id );
prefs = mContext.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
String type = prefs.getString("token_type","");
String access = prefs.getString("access_token","");
locks.unlock();
return response.request().newBuilder()
.header("Authorization", type + " " + access)
.build();
}
}catch (Exception e){
locks.unlock();
e.printStackTrace();
return null;
}
}
return null;
}
});