Retrofit: How do I retry the request again from okhttp interceptor? - android

Ive created an interceptor. In some cases, I want to retry the request 'n' number of time how do i do this?
class NetworkErrorHandler constructor(): Interceptor {
//Central error handling block for errors which has impact all over the app
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
var response = chain.proceed(request)
return
when (response.code) {
401 -> {
response
}
200 ->{
response
}
else -> {
var tryCount = 0
while (tryCount < 3) {
try {
response = chain.proceed(request)
tryCount++
}catch (e: java.lang.Exception){
tryCount++
}
}
response
}
}
}
}
It gives me this error:
Suppressed: java.lang.IllegalStateException: network interceptor must call proceed() exactly once
Do I have to do this here if yes, then how?

So i was able to make another call from the interceptor by using this line
response.close()
chain.call().clone().execute()
Full code according to the question:
//Central error handling block for errors which has impact all over the app
class NetworkErrorHandler constructor(): Interceptor {
var tryCount = 0
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
var response = chain.proceed(request)
return
when(response.code) {
401 - > {
response
}
200 - > {
response
}
else - > {
if (tryCount < 3) {
Thread.sleep(2000)
tryCount++
response.close()
chain.call().clone().execute()
}
response.newBuilder()
.code(401) // Whatever code
.body("".toResponseBody(null)) // Whatever body
.protocol(Protocol.HTTP_2)
.message("Network Error")
.request(chain.request())
.build()
}
}
}

Use .interceptors() to use Application Interceptor instead of .networkInterceptors() which are allowed to call .proceed() more than once.
More information: https://square.github.io/okhttp/interceptors/

I think you guided yourself from this other question How to retry HTTP requests with OkHttp/Retrofit? (If not, I suggest you do). The problem on your code is that your while is restricted only by trycount, but not by the result of the response. Try adding the validation regarding the status of the response in there too, so it doesn't execute the .proceed method two times in parallel.
// try the request
Response response = chain.proceed(request);
int tryCount = 0;
while (!response.isSuccessful() && tryCount < 3) {

This is how I approached it:
#Slf4j
public class HttpRetryInterceptor implements Interceptor {
#Override
public #NotNull Response intercept(Chain chain) throws IOException {
log.error("Making request for the first time.");
var request = chain.request();
Response response = null;
boolean responseOK = false;
byte tryCount = 0;
while (!responseOK && tryCount < 3) {
try {
Thread.sleep(5000 * tryCount);
response = chain.proceed(request);
log.info("Response is: {}",response);
log.info("Response message: {}",response.message());
responseOK = response.isSuccessful();
}catch (Exception e){
e.printStackTrace();
log.error("Request was not successful: {} . Retrying." , tryCount);
}finally{
assert response != null;
response.close();
tryCount++;
}
}
return response != null ? response : chain.proceed(request);
}
}
And then I added my Interceptor to my client with something like : new OkHttpClient.Builder().addInterceptor(new HttpRetryInterceptor()).build()

Related

Retrofit put method call update the item but give 400 response code in error in android

I am using Retrofit to update some information. The information is updated in the database. But i am getting 400 error code in response. At the same time the API works perfectly in postman.
I have double-checked that I'm sending the required headers and the API token which updated on every login. But I'm get 400 error still, while the information is updated.
this is
You are getting 401 as a status code that means unauthorized token you are passing or something wrong with the auth token
Check auth token you are passing is correct or not or you are passing it or not.
If you are not passing auth token in the api header then please pass it will resolve your error
this is api module class
var token = ""
if (prefs.contains(Constants.TOKEN_VALUE)) {
prefs.read(Constants.TOKEN_VALUE)?.let {
token = it
}
}
val httpInterceptor = HttpLoggingInterceptor()
httpInterceptor.level = httpLoggingLevel
val okHttp = OkHttpClient.Builder()
.addInterceptor(httpInterceptor).addInterceptor { chain ->
val requestBuilder = chain.request().newBuilder()
requestBuilder.addHeader("Accept", "application/json")
requestBuilder.addHeader("Cache-Control", "no-cache")
requestBuilder.addHeader("Content-Type", "application/x-www-form-urlencoded")
if (token.isNotEmpty() && Constants.apitokenheader==0) {
requestBuilder.addHeader("Authorization", token)
Log.d("apitoken", "providesBaseApiService: $token")
}
chain.proceed(requestBuilder.build())
}
.addInterceptor { chain ->
val request = chain.request()
val response = chain.proceed(request)
when(response.code()){
200, 201 -> response
204 -> response.newBuilder().code(200).body(ResponseBody.create(MediaType.get("application/json"), "1")).build()
else -> {
try {
response.body()?.byteStream()?.readBytes()?.toString(Charset.defaultCharset())?.let {
val obj = JSONObject(it)
val opt = obj.optString("message", "An error occurred, Please try again.")
Log.v("error message", opt)
Log.v("error message1", request.url().toString())
Log.v("error message2", response.code().toString())
val link = request.url().uri().toString()
val sub : String = link.substringAfterLast("v1")
Log.v("DripInventory", sub)
Log.v("DripInventory", link.indexOf("v1").let { if (it == -1) null else link.substring(it + 1) })
// link.indexOf("v1").let { if (it == -1) null else link.substring(it + 1) }
//response.newBuilder().code(422).message(opt).build()
response.newBuilder().code(response.code().toInt())
.message("Please contact Drip Inventory." +
"\n " +
"\nResponse Code: ${response.code()}" +
"\n " +
"\nApi Call: $sub")
.build()
}
}catch (e: Exception){
response
}
}
}
/*if (response.code() == 204) {
response.newBuilder().code(200).body(ResponseBody.create(MediaType.get("application/json"), "1")).build()
} else {
response
}*/
}
.hostnameVerifier{hostname, session ->
if (hostname == "dripinventory.com") return#hostnameVerifier true
if (hostname == "invalid.demo.dripinventory.com") return#hostnameVerifier true
Log.v("hostname", hostname)
true
}
.build()
val gson = GsonBuilder()
// Serializers
.registerTypeAdapter(CreateAssetRequest::class.java, CreateAssetRequestSerializer())
.registerTypeAdapter(UpdateAssetRequest::class.java, UpdateAssetRequestSerializer())
// Deserializers
.registerTypeAdapter(AssetRaw::class.java, AssetRawDeserializer())
.create()
val retrofit = Retrofit.Builder()
.client(okHttp)
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
return retrofit.create(CcAssetManagerApi::class.java)

How to check token expiration at interceptors android retrofit?

I would like to handle token expiration by myself and send request for new tokens. I have such condition:
sp.getLong("expires_in", 0) - sp.getLong("time_delta", 0) - System.currentTimeMillis() / 1000 <= 60
This condition checks when my token will become expired and I have to send a new request from interceptor. I saw this question also. I have created such interceptor:
class RefreshTokens(cont: Context) : Interceptor{
val context = cont
override fun intercept(chain: Interceptor.Chain): Response {
var tokenIsUpToDate = false
val sp = context.getSharedPreferences("app_data", 0)
if (sp.getLong("expires_in", 0) - sp.getLong("time_delta", 0) - System.currentTimeMillis() / 1000 <= 60) {
Singleton.apiService(context).getNewToken(ReqAccessToken(context.getSharedPreferences("app_data", 0).getString("refresh_token", ""))).enqueue(object : Callback<ResNewTokens>, retrofit2.Callback<ResNewTokens> {
override fun onResponse(call: Call<ResNewTokens>, response: retrofit2.Response<ResNewTokens>) {
if (response.isSuccessful) {
tokenIsUpToDate = true
}
}
override fun onFailure(call: Call<ResNewTokens>, t: Throwable) {
}
})
return if (tokenIsUpToDate) {
chain.proceed(chain.request())
} else {
chain.proceed(chain.request())
}
} else {
val response = chain.proceed(chain.request())
when (response.code) {
401->{
chain.request().url
response.request.newBuilder()
.header("Authorization", "Bearer " + context.getSharedPreferences("app_data", 0).getString("access_token", "")!!)
.build()
}
500 -> {
Toast.makeText(context, context.getString(R.string.server_error_500), Toast.LENGTH_SHORT).show()
}
}
return response
}
}
}
I can't imagine how to add return condition to my code. I know about Authentificator but when I use it I send one more request which response gives me 401 error for token updating. When I use Authentificator I send such requests:
Request with old access_token -> 401 error
Request for the new tokens -> 200 OK
Request with new access_token -> 200 OK
So I would like to remove 1 request which will give error and send request for a new tokens. But I have to problems:
I don't know how to fix my interceptor for solving this task
I don't know how to repeat request which I was going to make like in Authentificator
Maybe someone knows how to solve my problem?
Yes that is too much simple do not take is difficult, I also have same issue but i solve like this
So When the token is expred so the Retrofit give the
Error Code = 401
So you need to save the data of user Using sharedPref the userEmail or userName as well as userPassword So
When the user get token exipre message or error code 401 then you need to call a method to login the user again to show anything to the user using useremail and userpassword and then a fresh token generated then send that generated Token to the server and it will work in this case
I hope that will help
I would like to share my own solution which works good as I see:
class AuthToken(context: Context) : Interceptor {
var cont = context
override fun intercept(chain: Interceptor.Chain): Response {
val sp = cont.getSharedPreferences("app_data", 0)
if (sp!!.getLong("expires_in", 0) - sp.getLong("time_delta", 0) - System.currentTimeMillis() / 1000 <= 60 && !sp.getString("refresh_token", "")!!.isBlank()) updateAccessToken(cont)
val initialRequest = if (sp.getLong("expires_in", 0) - sp.getLong("time_delta", 0) - System.currentTimeMillis() / 1000 <= 60 && !sp.getString("refresh_token", "")!!.isBlank()) {
updateAccessToken(cont)
requestBuilder(chain)
} else {
requestBuilder(chain)
}
val initialResponse = chain.proceed(initialRequest)
return if (initialResponse.code == 401 && !sp.getString("refresh_token", "").isNullOrBlank() && sp.getLong("expires_in", 0) - sp.getLong("time_delta", 0) - System.currentTimeMillis() / 1000 <= 60) {
updateAccessToken(cont)
initialResponse.close()
val authorizedRequest = initialRequest
.newBuilder()
.addHeader("Content-type:", "application/json")
.addHeader("Authorization", "Bearer " + cont.getSharedPreferences("app_data", 0).getString("access_token", "")!!)
.build()
chain.proceed(authorizedRequest)
} else {
val errorBody = initialResponse.message
when {
}
if (initialResponse.code == 500) {
val thread = object : Thread() {
override fun run() {
Looper.prepare()
Toast.makeText(cont, cont.getString(R.string.server_error_500), Toast.LENGTH_SHORT).show()
Looper.loop()
}
}
thread.start()
}
initialResponse
}
}
private fun updateAccessToken(context: Context) {
val sp = context.getSharedPreferences("app_data", 0)
synchronized(this) {
val tokensCall = accessTokenApi()
.getNewToken(ReqAccessToken(context.getSharedPreferences("app_data", 0).getString("refresh_token", "")!!))
.execute()
if (tokensCall.isSuccessful) {
val responseBody = tokensCall.body()
val editor = sp.edit()
val localTime = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH).parse(tokensCall.headers()["Date"]!!)
Singleton.setServerTime(localTime!!.time / 1000, context)
editor.putString("access_token", Objects.requireNonNull<ResNewTokens>(responseBody).access_token).apply()
editor.putString("refresh_token", Objects.requireNonNull<ResNewTokens>(responseBody).refresh_token).apply()
editor.putLong("expires_in", responseBody!!.expires_in!!).apply()
} else {
when (tokensCall.code()) {
500 -> {
val thread = object : Thread() {
override fun run() {
Looper.prepare()
Toast.makeText(cont, cont.getString(R.string.server_error_500), Toast.LENGTH_SHORT).show()
Looper.loop()
}
}
thread.start()
}
401 -> {
Singleton.logOut(context)
}
}
}
}
}
private fun requestBuilder(chain: Interceptor.Chain): Request {
return chain.request()
.newBuilder()
.header("Content-type:", "application/json")
.header("Authorization", "Bearer " + cont.getSharedPreferences("app_data", 0).getString("access_token", "")!!)
.build()
}
private fun accessTokenApi(): APIService {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val dispatcher = Dispatcher()
dispatcher.maxRequests = 1
val client = OkHttpClient.Builder()
.addInterceptor(interceptor)
.connectTimeout(100, TimeUnit.SECONDS)
.dispatcher(dispatcher)
.readTimeout(100, TimeUnit.SECONDS).build()
client.dispatcher.cancelAll()
val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.API_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
return retrofit.create(APIService::class.java)
}
}
In general as I see I send request for token refreshing before send request with expired access_token. Maybe someone will have some suggestions or improvements for my solution :)

Kotlin and Retrofit : How to Handle HTTP 400 responses?

I am using Retrofit (2.6) on Android to implement a service which connects to a web server, and which requests that the server undertake some work. The relevant code can be summarized thus:
interface MyService {
#GET(START_WORK)
suspend fun startWork(#Query("uuid") uuid: String,
#Query("mode") mode: Int):
MyStartWorkResponse
}
// Do some other things, include get a reference to a properly configured
// instance of Retrofit.
// Instantiate service
var service: MyService = retrofit.create(MyService::class.java)
I can call service.startWork() with no problem and obtain valid results. However, in some conditions, the web server will return a 400 error code, with a response body which includes specific error information. The request is not malformed, however; it's just that there is another problem which should be brought to the user's attention. The trouble is, I can't tell what the problem is, because I don't get a response; instead, my call throws an exception because of the 400 error code.
I don't understand how to modify my code so that I can catch and handle 400 error responses, and get the information I need from the body of the response. Is this a job for a network interceptor on my okhttp client? Can anyone shed some light?
Use this code (KOTLIN)
class ApiClient {
companion object {
private val BASE_URL = "YOUR_URL_SERVER"
private var retrofit: Retrofit? = null
private val okHttpClientvalor = OkHttpClient.Builder()
.connectTimeout(90, TimeUnit.SECONDS)
.writeTimeout(90, TimeUnit.SECONDS)
.readTimeout(90, TimeUnit.SECONDS)
.build()
fun apiClient(): Retrofit {
if (retrofit == null) {
retrofit = Retrofit.Builder().baseUrl(BASE_URL)
.client(okHttpClientvalor)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return retrofit!!
}
}
}
object ErrorUtils {
fun parseError(response: Response<*>): ErrorResponce? {
val conversorDeErro = ApiClient.apiClient()
.responseBodyConverter<ErrorResponce>(ErrorResponce::class.java, arrayOfNulls(0))
var errorResponce: ErrorResponce? = null
try {
if (response.errorBody() != null) {
errorResponce = conversorDeErro.convert(response.errorBody()!!)
}
} catch (e: IOException) {
return ErrorResponce()
} finally {
return errorResponce
}
}
}
class ErrorResponce {
/* This name "error" must match the message key returned by the server.
Example: {"error": "Bad Request ....."} */
#SerializedName("error")
#Expose
var error: String? = null
}
if (response.isSuccessful) {
return MyResponse(response.body() // transform
?: // some empty object)
} else {
val errorResponce = ErrorUtils.parseError(response)
errorResponce!!.error?.let { message ->
Toast.makeText(this,message,Toast.LENGTH_SHORT).show()
}
}
Retrofit defines successful response as such:
public boolean isSuccessful() {
return code >= 200 && code < 300; }
which means you should be able to do something like this
class ServiceImpl(private val myService: MyService) {
suspend fun startWork(//query): MyResponse =
myService.startWork(query).await().let {
if (response.isSuccessful) {
return MyResponse(response.body()//transform
?: //some empty object)
} else {
throw HttpException(response)//or handle - whatever
}
}
}

How to retry Retrofit call on HTTP errors (401) when using RxJava?

My current Android Application is employing Retrofit(2.4.0) and RxJava(2.1.16) to execute my Web Service calls.
Im using Google SignIn for my User Authentication.
I want my Retrofit calls to detect HTTP 401 (UNAUTHORIZED) and attempt to Silently Login with Google Signin
then retry the Retrofit call.
My retrofit calls resemble this
#Headers(HEADER_ACCEPT_JSON)
#GET("resources")
Observable<Response<String>> getResources(#Header(HEADER_AUTHORIZATION) #NonNull final String authenticationToken, #QueryMap(encoded = true) #NonNull Map<String, Object> queryMap);
API_SERVICE.getResources(Login.getAuthorizationToken(), id)
.subscribeOn(Schedulers.io())
.subscribe(Network::manageResource, Network::handle));
From googling I can see that retry/retryWhen will only be triggered when an error occurs in my RxJava chain,
however HTTP 401 errors are not going to raise this condition.
As a newbie to RxJava how can I detect my HTTP 401 code and..
a). Execute Google SignIn Silent login
b). Silent login completes OK, retry my API call?
UPDATE
Ive got closer with the following code
#Headers(HEADER_ACCEPT_JSON)
#GET("resources")
Single<Response<String>> getResources(#Header(HEADER_AUTHORIZATION) #NonNull final String authenticationToken, #QueryMap(encoded = true) #NonNull Map<String, Object> queryMap);
API_SERVICE.getResources(Login.getAuthorizationToken(), id)
.subscribeOn(Schedulers.io())
.flatMap(new Function<Response<Article>,
SingleSource<Response<Article>>>() {
#Override
public SingleSource<Response<Article>> apply(final Response<Article> response) {
Log.d(TAG, "apply() called with: response = [" + response + "]");
if (response.isSuccessful()) {
return Single.just(response);
} else {
return Single.error(new RuntimeException());
}
}
})
.retryWhen(errors -> errors.take(1).flatMap(new Function<Throwable, Publisher<?>>() {
#Override
public Publisher<?> apply(final Throwable throwable) {
Log.d(TAG, "apply() called with: throwable = [" + throwable + "]");
Login.loginSilently().subscribe();
return Flowable.just("DELAY").delay(10, TimeUnit.SECONDS);
}
}))
.subscribe(Network::manageResource, Network::handle));
I do not like the Flowable.just("DELAY").delay() call and also even though I am now catching the exception and silently login in OK I get this exception
09-10 16:39:29.878 7651-7718/research.android E/Network: handle:
java.util.NoSuchElementException
at io.reactivex.internal.operators.flowable.FlowableSingleSingle$SingleElementSubscriber.onComplete(FlowableSingleSingle.java:116)
at io.reactivex.subscribers.SerializedSubscriber.onComplete(SerializedSubscriber.java:168)
at io.reactivex.internal.operators.flowable.FlowableRepeatWhen$WhenReceiver.onComplete(FlowableRepeatWhen.java:119)
at io.reactivex.internal.operators.flowable.FlowableFlatMap$MergeSubscriber.drainLoop(FlowableFlatMap.java:426)
at io.reactivex.internal.operators.flowable.FlowableFlatMap$MergeSubscriber.drain(FlowableFlatMap.java:366)
at io.reactivex.internal.operators.flowable.FlowableFlatMap$InnerSubscriber.onComplete(FlowableFlatMap.java:673)
at io.reactivex.subscribers.SerializedSubscriber.onComplete(SerializedSubscriber.java:168)
at io.reactivex.internal.operators.flowable.FlowableDelay$DelaySubscriber$OnComplete.run(FlowableDelay.java:139)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
09-10 16:39:29.878 7651-7678/research.android D/OkHttp: <-- HTTP FAILED: java.io.IOException: Canceled
How can I get the retrywhen to wait for the silentLogin to complete?
and
Whats causing the NoSuchElementException?
As far as I remember if you have error code > 300 then onError() will be called with Throwable which can ba cast to HttpException from where you can get error code returned by server so then you can call other function to make some "silent call"
When you initialize client:
Retrofit.Builder()
.baseUrl(baseUrl)
.client(createClient())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(ApiHandler(Schedulers.io()))
.build()
Error handler:
class ApiHandler(scheduler: Scheduler) : CallAdapter.Factory() {
private val original: RxJava2CallAdapterFactory
= RxJava2CallAdapterFactory.createWithScheduler(scheduler)
override fun get(returnType: Type, annotations: Array<Annotation>, retrofit: Retrofit): CallAdapter<*, *>?
= original.get(returnType, annotations, retrofit)?.let { Wrapper(it) }
private class Wrapper<R>(private val wrapped: CallAdapter<R, *>) : CallAdapter<R, Any> {
override fun adapt(call: Call<R>?): Any? {
call ?: return null
val result = wrapped.adapt(call)
return when (result) {
is Maybe<*> -> result.onErrorResumeNext(Function { Maybe.error(wrap(it)) })
is Single<*> -> result.onErrorResumeNext { Single.error(wrap(it)) }
is Completable -> result.onErrorResumeNext { Completable.error(wrap(it)) }
is Flowable<*> -> result.onErrorResumeNext(Function { Flowable.error(wrap(it)) })
is Observable<*> -> result.onErrorResumeNext(Function { Observable.error(wrap(it)) })
else -> result
}
}
override fun responseType(): Type = wrapped.responseType()
private fun wrap(throwable: Throwable) = when (throwable) {
is HttpException -> {
val exception = ApiException.http(throwable)
toLog("ex - ${exception.message}")
exception
} // We had non-200 http error
is JsonSyntaxException -> ApiException.parse(throwable) // We had json parsing error
is SocketTimeoutException -> ApiException.timeout(throwable) // A network error happened
is IOException -> ApiException.network(throwable) // A network error happened
else -> ApiException.unknown(throwable) // We don't know what happened. We need to simply convert to an unknown error
}
}
}
Api exception class:
class ApiException internal constructor(message: String,
/** Response object containing status code, headers, body, etc. */
val response: ErrorResponse?,
/** The event kind which triggered this error. */
#ApiError val error: Int,
exception: Throwable?) : RuntimeException(message, exception) {
companion object {
fun http(exception: HttpException): ApiException {
val response = exception.response()
var errorResponse: ErrorResponse? = null
val message = if (response == null) {
if (exception.message().isEmpty()) exception.code().toString() else exception.message()
} else {
// here you can check error code and throw needed exception
val errorBody = response.errorBody()?.string().toString()
if (errorBody.isNotEmpty()) {
toLog("ApiException: $errorBody")
}
try {
errorResponse = GsonBuilder().create().fromJson(errorBody, ErrorResponse::class.java)
errorResponse?.toString() ?: errorBody
} catch (e: Exception) {
e.printStackTrace()
response.raw().message()
}
}
return ApiException(message, errorResponse, ApiError.HTTP, exception)
}
fun network(exception: IOException): ApiException {
return ApiException(exception.message ?: "network", null, ApiError.NETWORK, exception)
}
fun parse(exception: JsonSyntaxException): ApiException {
return ApiException(exception.message ?: "parse", null, ApiError.CONVERSION, exception)
}
fun unknown(exception: Throwable): ApiException {
return ApiException(exception.message ?: "unknown", null, ApiError.UNKNOWN, exception)
}
fun timeout(exception: SocketTimeoutException): ApiException {
return ApiException("Connection timed out", null, ApiError.TIMEOUT_EXCEPTION, exception)
}
}
}
And when calling request
yourRequest.compose { observable ->
observable.retryWhen { flow ->
flow.ofType(ApiException::class.java).flatMap {
when {
it.error == ApiError.TIMEOUT_EXCEPTION -> Flowable.empty<T>()
it.error == ApiError.NETWORK -> getSnackBarFlowable().flatMap { if (it) Flowable.just(it) else Flowable.empty<T>() }
else -> Flowable.error(it)
}
}
}
}.subscribe({}, {})
getSnackBarFlowable() is get from fragment. you can use something else
fun getSnackBarFlowable(): Flowable<Boolean> = Flowable.create({ subscriber ->
if (view == null) {
subscriber.onNext(false)
} else {
val snackBar = Snackbar.make(activity!!.currentFocus, R.string.error_connection_fail, Snackbar.LENGTH_INDEFINITE)
snackBar.setAction("Retry") { subscriber.onNext(true) }
snackBar.show()
}
}, LATEST)
I know, quite enough of code. But this solution is really helpful for me in different projects
To solve 401 Unauthorized Error try to implement AuthInterceptor to your OkHttpClient.
BasicAuthInterceptor interceptorAuth = new BasicAuthInterceptor(yourToken);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptorAuth)
.build();
builder.client(client);
If your authToken is expired or bad try to gain new.
public class BasicAuthInterceptor implements Interceptor {
private String yourToken;
public BasicAuthInterceptor(String token) {
this.yourToken = token;
}
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request authenticatedRequest = request.newBuilder()
.header("Authorization", format("token %s", yourToken)).build();
Response response = chain.proceed(authenticatedRequest);
boolean unauthorized = response.code() == 401;
if (unauthorized) {
Request modifiedRequest = request.newBuilder()
.header("Authorization", format("token %s", getNewToken())).build();
response = chain.proceed(modifiedRequest);
}
return response;
}
}

Okhttp3 request returning empty response as well as different headers than Postman

I'm trying to parse the JSON from this URL, https://fantasy.premierleague.com/drf/elements but the response I get from okhttp and Postman are different. I've also used online API testers and I get a response back with the full JSON there as well. I'm not sure why my code isn't working.
Can anyone help me figure this out?
Here's the code I'm using for quick testing.
val request = Request.Builder()
.url("https://fantasy.premierleague.com/drf/elements")
.get()
.addHeader("accept", "*/*")
.addHeader("accept-encoding", "gzip, deflate")
.addHeader("cache-control", "no-cache")
.addHeader("connection", "keep-alive")
.cacheControl(CacheControl.FORCE_NETWORK)
.build()
val httpClient = OkHttpClient()
httpClient.newCall(request).enqueue(object : okhttp3.Callback {
override fun onFailure(call: okhttp3.Call?, e: IOException?) {
Timber.d("FPL response failed = " + e?.message.toString())
}
override fun onResponse(call: okhttp3.Call?, response: okhttp3.Response?) {
if (response!!.isSuccessful) {
val responseBody = response.body()?.string()
try {
val obj = JSONObject(responseBody)
Timber.d("FPL response = " + obj.toString())
} catch (t: Throwable) {
Timber.e("Could not parse malformed JSON: " + t.message)
}
Timber.d("FPL response = $response")
Timber.d("FPL headers = " + response.headers())
Timber.d("FPL body = " + responseBody)
} else {
Timber.d("FPL response failed = " + response.body().toString())
}
}
})
I tried replicating Postman's code snippet headers which are:
Request request = new Request.Builder()
.url("https://fantasy.premierleague.com/drf/elements")
.get()
.addHeader("cache-control", "no-cache")
.addHeader("postman-token", "05ae03ef-cf44-618c-a82c-5762e245b771")
.build();
but sadly no luck there either.
Log:
D/HomeController$onAttach:L135: FPL response = Response{protocol=http/1.1, code=200, message=OK, url=https://fantasy.premierleague.com/drf/elements}
D/HomeController$onAttach:L138: FPL headers =
Server: Varnish
Retry-After: 0
Content-Type: application/json
Content-Length: 0
Accept-Ranges: bytes
Date: Tue, 15 Aug 2017 22:17:18 GMT
Via: 1.1 varnish
Connection: close
X-Served-By: cache-jfk8123-JFK
X-Cache: MISS
X-Cache-Hits: 0
X-Timer: S1502835438.419014,VS0,VE0
D/HomeController$onAttach:L139: FPL body =
As you can see, the body is empty and "Connection" is close.
Here's the response headers that Postman gets, much different.
Accept-Ranges →bytes
Age →14
Allow →GET, HEAD, OPTIONS
Cache-Control →no-cache, no-store, must-revalidate, max-age=0
Connection →keep-alive
Content-Encoding →gzip
Content-Language →plfplen
Content-Length →39518
Content-Type →application/json
Date →Tue, 15 Aug 2017 00:54:32 GMT
Edge-Control →max-age=60
Fastly-Debug-Digest →fdb44d2dd7c0b26c639a8b3476f8c63661c68707cc3b9446f8ed3941cd3fe01e
Server →nginx
Vary →Accept-Encoding
Via →1.1 varnish
Via →1.1 varnish
X-Cache →HIT, MISS
X-Cache-Hits →1, 0
X-Frame-Options →DENY
X-Served-By →cache-lcy1146-LCY, cache-jfk8143-JFK
X-Timer →S1502758472.116470,VS0,VE82
as you can see, "Connection" says keep alive.
Is there something I'm missing in my Request.Builder() to get this to work?
EDIT:
So, I decided to try making the request using an AsyncTask, and got the full JSON back as you can see in the log under. I don't understand why Okhttp3 isn't working.
val url = params[0]
var stream: InputStream? = null
var connection: HttpsURLConnection? = null
var result: String? = null
try {
connection = url?.openConnection() as HttpsURLConnection?
// Timeout for reading InputStream arbitrarily set to 3000ms.
connection?.readTimeout = 3000
// Timeout for connection.connect() arbitrarily set to 3000ms.
connection?.connectTimeout = 3000
// For this use case, set HTTP method to GET.
connection?.requestMethod = "GET"
// Already true by default but setting just in case; needs to be true since this request
// is carrying an input (response) body.
connection?.doInput = true
// Open communications link (network traffic occurs here).
connection?.connect()
val responseCode = connection?.responseCode
if (responseCode != HttpsURLConnection.HTTP_OK) {
throw IOException("HTTP error code: " + responseCode)
}
// Retrieve the response body as an InputStream.
stream = connection?.inputStream
Timber.d("httpurl stream = " + connection?.inputStream.toString())
if (stream != null) {
// Converts Stream to String with max length of 500.
result = readStream(stream, 500)
}
} finally {
// Close Stream and disconnect HTTPS connection.
if (stream != null) {
stream.close()
}
if (connection != null) {
connection.disconnect()
}
}
return result
Log:
D/HomeController$NetworkA:L266: httpurl response = [{"id":1,"photo":"48844.jpg","web_name":"Ospina","team_code":3,"status":"a","code":48844,"first_name":"David","second_name":"Ospina","squad_number":13,"news":"","now_cost":50, .... }]
Your problem is User-Agent header. Here is a working example:
fun syncGetOkHttp() {
println("\n===")
println("OkHttp")
println("===")
val client = OkHttpClient().newBuilder()
.addNetworkInterceptor { chain ->
val (request, response) = chain.request().let {
Pair(it, chain.proceed(it))
}
println("--> ${RequestLine.get(request, Proxy.Type.HTTP)})")
println("Headers: (${request.headers().size()})")
request.headers().toMultimap().forEach { k, v -> println("$k : $v") }
println("<-- ${response.code()} (${request.url()})")
val body = if (response.body() != null)
GZIPInputStream(response.body()!!.byteStream()).use {
it.readBytes(50000)
} else null
println("Response: ${StatusLine.get(response)}")
println("Length: (${body?.size ?: 0})")
println("""Body: ${if (body != null && body.isNotEmpty()) String(body) else "(empty)"}""")
println("Headers: (${response.headers().size()})")
response.headers().toMultimap().forEach { k, v -> println("$k : $v") }
response
}
.build()
Request.Builder()
.url(url)
.header("Accept", "application/json")
.header("User-Agent", "Mozilla/5.0")
.build()
.let { client.newCall(it).execute() }
}

Categories

Resources