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
Related
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()
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 ?
I have a Retrofit + Gson setup, which leaks the connection due to
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING
The REST API I work with returns a HTML error page instead of a JSON response on internal server errors (500).
Is there a way to handle this case on a client level? Let's say if I receive an error response with status code 500, Retrofit or GSON should not try to deserialise the response.
If you are using OkHttpClient as your http client for Retrofit you can add an interceptor to the OkHttpClient and if the request returns an error you can do something with it. Example:
public class InternalServerErrorInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
if (response.code() == HttpURLConnection.HTTP_INTERNAL_ERROR) {
//do whatever you want to do with the response here
}
return response;
}
}
...
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new InternalServerErrorInterceptor())
.build();
RestAdapter.Builder builder = new RestAdapter.Builder()
.setClient(new Ok3Client(client))
.setEndpoint(API_BASE_URL); //Ok3Client: https://github.com/JakeWharton/retrofit1-okhttp3-client
I am creating an application with retrofit2 for network calls. I need call multiple API in Single Activity. Now I am facing the 403-forbidden error. If I call only one API it is working fine. But if I use multiple API calls one by one then I am facing this error.
My CreateService method is below:
public static <S> S createService(Class<S> serviceClass, final String authToken) {
if (authToken != null) {
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request original = chain.request();
// Request customization: add request headers
Request.Builder requestBuilder = original.newBuilder()
.header("Authorization-Token", authToken)
.method(original.method(), original.body());
Request request = requestBuilder.build();
return chain.proceed(request);
}
});
}
// OkHttpClient client = httpClient.readTimeout(60, TimeUnit.SECONDS).connectTimeout(60, TimeUnit.SECONDS).build();
Dispatcher dispatcher = new Dispatcher(Executors.newFixedThreadPool(200));
dispatcher.setMaxRequests(200);
dispatcher.setMaxRequestsPerHost(1);
OkHttpClient okHttpClient = httpClient.dispatcher(dispatcher).connectionPool(new ConnectionPool(100, 30, TimeUnit.SECONDS)).build();
Retrofit retrofit = builder.client(okHttpClient).build();
return retrofit.create(serviceClass);
}
What is wrong in my code.. How can I handle this?
Are you sure that string is not empty string?
Could you please add log interceptor and set the log level and provide a log?
compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'
And sth like this :
OkHttpClient.Builder okBuilder = new OkHttpClient.Builder();
okBuilder.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC).setLevel
(HttpLoggingInterceptor.Level.BODY).setLevel(HttpLoggingInterceptor.Level.HEADERS))
im trying to consume an api that has that authorization header, i can get a 200 response in Postman with all data but cant get it to work in retrofit
May be you need add the Token using OkHttp Interceptor.
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(mTokenInterceptor)
.build();
then add it to Retrofit:
Retrofit retrofit = new Retrofit.Builder()
.client(client)
.baseUrl(base_url)
.build();
the mTokenInterceptor:
Interceptor mTokenInterceptor = new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (mToken != null) {
Request.Builder requestBuilder = request.newBuilder()
.addHeader("Authorization", mToken);
Request newRequest = requestBuilder.build();
return chain.proceed(newRequest);
}
return chain.proceed(request);
}
};
when you get the Token, just assign the mToken,
You can try something like below, just a crude example
#GET("your server url goes here")
Call<Your_Model_Class> getServerData(#Header("Authorization") String token);
Pass your token to getServerData method.