Retrofit 2.x.x + Okhttp3 intercepter + Dagger +Rx - Connection not closed - android

I'm using
Retrofit 2.2.0 + okhttp3 Intercepter + GSONConverterFactory (Dagger2)+ RxJava2.
When checking on the server, it seem like the connection made by the app is kept alive, and not closed even after response is received.
So basically I have
App module - Where Retrofit with GSONCOnverterFactory and okhttp client and it's interceptor is present.
App Module class :
#Singleton
#Provides
protected MyService providesMyService(#Named("MyService") Retrofit retrofit) {
return retrofit.create(MyService.class);
}
#Singleton
#Provides
#Named("MyService")
protected Retrofit providesMyRetrofit(GsonConverterFactory factory) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(3000, TimeUnit.MILLISECONDS);
builder.readTimeout(3000, TimeUnit.MILLISECONDS);
builder.addInterceptor(new MyInterceptor());
builder.addNetworkInterceptor(new CachingControlInterceptor());
//caching
builder.cache(MyCache.getCache());
builder.addInterceptor(new HttpLoggingInterceptor()
.setLevel(loggingLevel));
try {
return new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(builder.build())
.addConverterFactory(factory)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
} catch (Exception e) {
}
}
My Interceptor :
#Override
public Response intercept(Chain chain) throws IOException {
final Charset UTF8 = Charset.forName("UTF-8");
final String userPassword = USERNAME + ":" + PASSWORD;
Request original = chain.request();
Request request = original.newBuilder()
.header("User-Agent", SOME_AGENT)
.header("Authorization", "Basic " + new String(Base64.encodeBase64(userPassword.getBytes(UTF8)), UTF8))
.header("Content-Type", "application/json; charset=utf-8")
.header("Keep-Alive", "timeout = 3")
.method(original.method(), original.body())
.build();
Response response = chain.proceed(request);
return response;
}
this is Rx Code :
MyApp.getInstance().getAppComponent()
.getMyService()
.getData(queryMap, resultValues)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe((MyResponse myResponse) -> {
//success result handled here
}, throwable -> {
//exception result handled here
});
I don't see any warning in code or in logcat. Passing Keep-Alive : timeout in header is not helping too.
How can I verify from the app side if connection is closed properly, or if it is kept open even after the response is received on the app ?

Related

Retrofit2 send Bearer Token

I've tried sending the token with a HeaderMap but get a 401 code response. The way my project is setup is that I have a separate file for my ApiClient and I have a OkHttpClient Interceptor and a HttpLoggingInterceptor to see whats going on, however I can't get the Bearer Token to work. I've seen solutions that add it to the interceptor as a header in the interceptor and I've tried this but since my token is saved in SharedPreferences I can't get it to work in the ApiClient class I have.
This is the ApiClient
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
Gson gson = new GsonBuilder().serializeNulls().setLenient().create();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
#NotNull
#Override
public okhttp3.Response intercept(#NotNull Chain chain) throws IOException {
Request originalRequest = chain.request();
Request newRequest = originalRequest.newBuilder()
//I would add the header here
//I tried this but it says on "ApiClient.this" cannot be referenced from static context
// .header("Authorization" , SharedPreferencesHelper.getUserToken(ApiClient.this));
.build();
return chain.proceed(newRequest);
}
})
.addInterceptor(interceptor)
.build();
retrofit = new Retrofit.Builder()
.baseUrl("http://192.168.0.6:8000/api/")
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
This is the method from SharedPreferencesHelper.getUserToken(MainActivity.this)
public static String getUserToken(Context context) {
SharedPreferences sp = getSharedPreferences(context);
return sp.getString(USER_TOKEN, null);
}
This is the current call where the response is 401, If I don't add the Accept => application/json the response url is incorrect and also returns a html page when I need a simple response return response("LoggedOut", 200); //this is the response in the api
Map<String, String> headers = new HashMap<>();
headers.put("Accept", "application/json");
headers.put("Token", SharedPreferencesHelper.getUserToken(MainActivity.this));
Call<Void> call = apiInterface.LogoutUser(headers);
call.enqueue(new Callback<Void>() {
// onResponse and onFailure here
}
For example without the Accept header this is the response in the Logcat
D/OkHttp: --> GET http://192.168.0.6:8000/api/logout
D/OkHttp: Token: wE1Y8IxJpwyXtvw0fYoXZAlQ6qCx24YtzonQIeJBQSHmNppe0Sn1kLYDgZKCw4MKbpab4Vspf61Nzer1
D/OkHttp: --> END GET
D/OkHttp: <-- 200 OK http://192.168.0.6:8000/login
//a bunch of html that's the web page at this route, notice the /api is missing
How can I send this correctly?
EDIT:
I"m using a Laravel project for the backend and this is the relevant route
Route::middleware('auth:sanctum')
->get('/logoutApi', function (Request $request) {
$request->user()->tokens()->delete();
return response("LoggedOut", 202);
});
create class Authenticator, like:
const val HEADER_TOKEN_FIELD = "Authorization"
class ClassAuthenticator(
private val pref: SharedPref
) : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
return response.request().newBuilder()
.header(HEADER_TOKEN_FIELD, pref.getToken())
.build()
}
}
then add interceptor in your client with:
val httpClient = OkHttpClient.Builder()
.authenticator(ClassAuthenticator(pref))
.addInterceptor { chain ->
val request = chain.request()
val httpRequest = request.newBuilder()
.addHeader(HEADER_TOKEN_FIELD,
"Bearer ${pref.getToken()}")
.build()
val response = chain.proceed(httpRequest)
response
}
.build()

Retrofit with OkHTTP not set Content-Type with #FormUrlEncoded

I'm trying to implement auth via x-www-form-urlencoded with Retrofit 2 on Android but faced a problem, that Header Content-Type not set with #FormUrlEncoded annotation, as well as I'm trying to set it manually, but when I'm setting it with a typo like Cotent-Type it works correctly and I can see it in headers.
Retrofit version: 2.4.0
So my question: why #FormUrlEncoded not set a content type as well as #Header annotation or what can remove it from headers.
My request:
#FormUrlEncoded
#POST("account/login")
Single<LoginResponse> login(#Field("memberId") String memberId,
#Field("pin") String pin);
OkHTTP/Retrofit provider with interceptors:
#Singleton
#Provides
Retrofit provideRetrofit(final OkHttpClient client, final Moshi moshi) {
return new Retrofit.Builder()
.baseUrl(Configuration.BASE_URL)
.client(client)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
#Provides
OkHttpClient provideOkHttpClient(#AppContext final Context context) {
final OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
.followRedirects(true)
.followSslRedirects(true)
.addInterceptor(createLanguageInterceptor(context));
if (BuildConfig.DEBUG) {
builder.addInterceptor(new LoggingInterceptor());
}
return builder.build();
}
Interceptor createLanguageInterceptor(#AppContext final Context context) {
Locale current = context.getResources().getConfiguration().locale;
return chain -> {
Request.Builder builder = chain.request().newBuilder();
builder.addHeader("Accept-Language", current.getLanguage());
Request request = builder.build();
Response response = chain.proceed(request);
return response;
};
}
As a workaround, I've implemented the following interceptor:
Interceptor createHeaderTransformationInterceptor() {
return chain -> {
final Request request = chain.request();
String dataType = request.header("Data-Type");
final Request resultRequest = dataType == null
? request
: chain.request().newBuilder()
.removeHeader("Data-Type")
.addHeader("Content-Type", dataType)
.build();
return chain.proceed(resultRequest);
};
}
and it works fine with the following annotation:
#Headers({"Data-Type: application/x-www-form-urlencoded"})
UPD: the reason that my interceptor didn't see that is in a place where the content type is stored. The right way to see that header in an interceptor:
if (requestBody.contentType() != null) {
logger.log("Content-Type: " + requestBody.contentType());
}
if (requestBody.contentLength() != -1) {
logger.log("Content-Length: " + requestBody.contentLength());
}
By this Request
#FormUrlEncoded
#POST("account/login")
Single<LoginResponse> login(#Field("memberId") String memberId,
#Field("pin") String pin);
method #POST and #FormUrlEncoded automatic add
Content-Type: application/x-www-form-urlencoded in header you can check in log by
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(interceptor.setLevel(HttpLoggingInterceptor.Level.BODY))
.connectTimeout(2, TimeUnit.MINUTES)
.writeTimeout(2, TimeUnit.MINUTES)
.readTimeout(2, TimeUnit.MINUTES)
.build();
it print all log in verbose mode

Retrofit returns HTTP 401 for call running in background

I have a strange problem with one of my retrofit call,it works fine when the app is in the background(recent list)
I have a call through which i update my widget data,the problem is when the app is cleared of from the recent list,the call gives HTTP 401 unauthorized response.
however i pass the same bearer token with it.
please have a look at the code and suggest some help
public static OkHttpClient getOkhttpClient() {
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request newRequest = chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + TokenGenerator.getToken())
.build();
return chain.proceed(newRequest);
}
}).build();
return client;
}
public static Retrofit getClient() {
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(getOkhttpClient())
.addConverterFactory(JacksonConverterFactory.create())
.build();
}
return retrofit;
}

Retrofit is returning cached response

I am using Retrofit in an Android application. When I hit an API with token to get user information it gives cached(previous) response. Whenever I logged out and log in again API gives previous user detail, I tested API in Postman it works fine there.
I have tried some solutions I searched but nothing is working.
Response header that I am getting is
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Kestrel
Date: Mon, 08 Jan 2018 09:35:26 GMT
Below is ApiClient class
public class ApiClient {
public static final String BASE_URL = "http://XX.XXX.XXX.XX/api/";
private static Retrofit authRetrofit = null;
private static Retrofit retrofit = null;
public static Retrofit getClient() {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(ScalarsConverterFactory.create())
.build();
}
return retrofit;
}
public static Retrofit getAuthorizeClient(final String token) {
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
.addHeader("Authorization", "Bearer " + token)
.addHeader("Cache-control", "no-cache")
.method(original.method(), original.body())
//.cacheControl(CacheControl.FORCE_NETWORK)
.build();
return chain.proceed(request);
}
});
OkHttpClient client = httpClient.cache(null).build();
if (authRetrofit == null) {
authRetrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(ScalarsConverterFactory.create())
.client(client).build();
}
return authRetrofit;
}
}
In your httpClient interceptor, try adding cache control header:
.addHeader("Cache-control", "no-cache");
EDIT
Just noticed you are on Retrofit2
original.newBuilder().header("Cache-control", "no-cache");
Alternatively try adding it as an annotation to your API method:
#Headers("Cache-control: no-cache")
Response callApi();
According to Docs.
EDIT 2
Okay I suspect it's because of your if condition, your authRetrofit wouldn't be updated if condition failed. Try removing it
if (authRetrofit == null)
Help this helps.
Use Post request instead, I've faced the same problem, rectified by changing my GET method to post and send one random string as a Field.

How to Add GSON CallAdapterFactory only when Retrofit Response is Succesfull

I have a REST Server, My problem is whenever my response is not successful i want to parse the error from response body and show it to the user (pass the error info to the calling Activity)
#Named("rest_api")
#Singleton
#Provides
Interceptor provideRESTInterceptor(final UserManager userManager) {
return new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
String token = userManager.getJwt();
Request request = original.newBuilder()
.header("Accept", "application/json")
.header("Authorization", "Bearer "+token)
.method(original.method(), original.body())
.build();
Log.i(LOG_TAG, "REQUEST URL "+request.uri());
Response response = chain.proceed(request);
if(!response.isSuccessful()) {
// How do i send this error to my Activity.
APIError apiError = new APIError(response.code(),response.body().string());
}
// Customize or return the response
return response;
}
};
}
I am using RxJavaConverter
#Named("rest_api")
#Singleton
#Provides
Retrofit provideRESTRetrofit(#Named("rest_api")OkHttpClient client, Converter.Factory converter, CallAdapter.Factory rxJava) {
return new Retrofit.Builder()
.baseUrl(PUBLIC_PRODUCTION_URL)
.client(client)
.addCallAdapterFactory(rxJava)
.addConverterFactory(converter)
.build();
}
Since Retrofit 2.0, even if the response is not successful it tries to convert the data with given GSON into (POJO), and thus throw an error, and i lost the actual message of the error.
As #david:mihola suggested it worked, using Retrofit Response object i was able to achieve this.
Response<MyObject> response;
if(response.isSucessful) {
MyObject obj = response.body();
} else {
// You can process your error message from the reponse
}
It really saved me from lot manual GSON parsing

Categories

Resources