I have trying to parse actual response body even if server returns 401 HTTP Exception.
protected inline fun <RESPONSE : ParentResponse> executeNetworkCall(
crossinline request: () -> Single<RESPONSE>,
crossinline successful: (t: RESPONSE) -> Unit,
crossinline error: (t: RESPONSE) -> Unit) {
request().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ t: RESPONSE ->
errorHandler!!.checkApiResponseError(t)?.let {
listener?.onErrorWithId(t.message!!)
error(t)
return#subscribe
}
successful(t)
}
,
{ t: Throwable ->
listener?.onErrorWithId(t.message!!)
}
)
}
This is what I have written. It parses response and error very well when both are separate in usual ways. But I want to parse success response when I get even 401 HTTP Exception.
Thanks in advance..
Response with 401 HTTP looks like below.
401 Unauthorized - HTTP Exception
{"Message":"Authentication unsuccessful","otherData":"//Some data"}
By the way I have to check HTTP error code..
if (statusCode==401){
print("Authentication unsuccessful")
}
You can use Retrofit's Response class for that purpose, which is a wrapper over your response object, it has both your response's data and error bodies and also the success state, so instead of doing Single<RESPONSE> use Single<Response<RESPONSE>>.
Parsing the response object can be something like this:
{ t: Response<RESPONSE> ->
if (t.isSuccessful())
// That's the usual success scenario
else
// You have a response that has an error body.
}
,
{ t: Throwable ->
// You didn't reach the endpoint somehow, maybe a timeout or an invalid URL.
}
Related
I'm implementing Twitter OAuth flows as per:
https://developer.twitter.com/en/docs/authentication/guides/log-in-with-twitter
I am getting a response back for the first step (oauth/request_token) which has a 200 code, but the response body is completely empty.
I'm using Retrofit to call the API, and have hooked up an interceptor OkHttpClient to debug the response like so:
val client = OkHttpClient.Builder().also { builder ->
builder.addInterceptor { chain ->
val request = chain.request()
val response = chain.proceed(request)
response
}
}.build()
Then setting up Retrofit like so:
Retrofit.Builder()
.baseUrl(TWITTER_AUTH_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
.create(TwitterAuthRetrofit::class.java)
.getRequestToken(
authorizationHeaders
).enqueue(object : Callback<TwitterRequestToken> {
override fun onResponse(call: Call<TwitterRequestToken>, response: Response<TwitterRequestToken>) {
onSuccess(response.body())
}
override fun onFailure(call: Call<TwitterRequestToken>, t: Throwable) {
onFailure()
}
})
When I debug in the interceptor, I can see the response is successful (200) but the response body is empty, which I think is causing my Gson deserialization to fail.
The result of calling response.body.contentLength() in the interceptor is -1.
The result of calling response.code in the interceptor is 200.
Here is the model I am attempting to deserialize the response body to:
data class TwitterRequestToken(
#SerializedName(value = "oauth_token")
val token: String,
#SerializedName(value = "oauth_token_secret")
val tokenSecret: String,
#SerializedName(value = "oauth_callback_confirmed")
val callbackConfirmed: Boolean
)
Note I am using #SerializedName to provide the keys for the response body, whilst the names of my properties are arbitrary to our app (we use camel case). I add a GsonConverterFactory to the Retrofit instance using the builder and have done this in the same way for many other requests before with no issues.
Here is the response I am getting from the API, which I am looking at via debugging in the interceptor above:
Response{protocol=h2, code=200, message=, url=https://api.twitter.com/oauth/request_token}
And here is the cause message from the Throwable I am getting in the onFailure callback from Retrofit:
com.google.gson.stream.MalformedJsonException:
Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $
Has anyone got any idea what might cause this?
Finally figured it out, hope this helps someone in future...
The response body from the Twitter API for oauth/request_token isn't encoded as JSON; you will need to read it from the response buffer. Specifically, when implementing the API with Retrofit, you will want your Retrofit interface to return ResponseBody (rather than your custom class), remove GSON from the Retrofit builder and, in the onResponseCallback from Retrofit, write the following code to read the buffer to a string, then split the string on & to get each key val pair, then you can split each of these on = and make sure you have all 3 values before constructing your model:
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
response.body()?.also { body ->
body.source().readString(Charsets.UTF_8).split('&').map { param ->
param.split('=').let { keyVal ->
keyVal[0] to keyVal[1]
}
}.toMap().let { paramMap ->
val oauthToken = paramMap["oauth_token"]
val oauthTokenSecret = paramMap["oauth_token_secret"]
val oauthCallbackConfirmed = paramMap["oauth_callback_confirmed"]?.toBoolean()
if (oauthToken == null || oauthTokenSecret == null || oauthCallbackConfirmed == null) {
onFailure()
} else {
onSuccess(
TwitterRequestToken(
oauthToken,
oauthTokenSecret,
oauthCallbackConfirmed
)
)
}
}
} ?: onFailure()
}
I am using the following method to handle my requests
override fun intercept(chain: Interceptor.Chain): Response = chain.proceed(chain.request())
.let { originalResponse ->
Log.i("AMIRA999", "code : " + originalResponse.code())
when (originalResponse.code()) {
200 -> {
Log.i("AMIRA999", "body : " + getErrorResponse(originalResponse))
originalResponse
}
401, 404 -> {
Log.i("AMIRA999", "body : " + getErrorResponse(originalResponse))
originalResponse
/*return originalResponse.mapToBody(
originalResponse.body()?.contentType(),
getErrorResponse(originalResponse)
)*/
}
else -> {
Log.i("AMIRA999", "body : " + originalResponse.body().toString())
throw BadRequestException()
}
}
}
the method work perfect when the code is 200, but it crash if the code is 404 or 401
what I need to keep returning the json comes from server and does not crash to be able to handle it with error message
how can I do that ?
the crash that I got is the following
retrofit2.HttpException: HTTP 401 UNAUTHORIZED
at com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory$BodyCallAdapter$adapt$2.onResponse(CoroutineCallAdapterFactory.kt:104)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:129)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:206)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
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)
You use retrofit2-kotlin-coroutines-adapter and the exception throwing is by design. Any non-2xx HTTP response such as 401 will throw an exception. You can see this for yourself in the library source code
if (response.isSuccessful) {
deferred.complete(response.body()!!)
} else {
deferred.completeExceptionally(HttpException(response))
}
But this is not a problem. You can still access the response and your JSON by doing catch (e: HttpException) and then calling val yourJson = e.response()?.body() as? YourJson.
Note that retrofit2-kotlin-coroutines-adapter is deprecated and that you should migrate to Retrofit 2.6.0 or newer. Then you can prefix your Retrofit interface functions with suspend so you can write nice idiomatic Kotlin code.
Retrofit 2 has a different concept of handling "successful" requests than Retrofit 1. In Retrofit 2, all requests that can be executed (sent to the API) and for which you’re receiving a response are seen as "successful". That means, for these requests the onResponse callback is fired and you need to manually check whether the request is actually successful (status 200-299) or erroneous (status 400-599).
If the request finished successfully, we can use the response object and do whatever we wanted. In case the error actually failed (remember, status 400-599), we want to show the user appropriate information about the issue.
Example
Error Object
Let’s assume your API sends a JSON error body like this:
{
statusCode: 409,
message: "Email address already registered"
}
Note: you can see your JSON error body by printing response.errorBody()
To avoid these bad user experiences, we’re mapping the response body to a Java object, represented by the following class.
class APIError {
private val statusCode:Int = 0
private val message:String
fun status():Int {
return statusCode
}
fun message():String {
return message
}
}
Error Handler
object ErrorUtils {
fun parseError(response:Response<*>):APIError {
val converter = ServiceGenerator.retrofit()
.responseBodyConverter(APIError::class.java, arrayOfNulls<Annotation>(0))
val error:APIError
try
{
error = converter.convert(response.errorBody())
}
catch (e:IOException) {
return APIError()
}
return error
}
}
Error Handler in Action
Now you can handle error in API response using ErrorUtils like the following.
val call = service.me()
call.enqueue(object:Callback<User>() {
fun onResponse(call:Call<User>, response:Response<User>) {
if (response.isSuccessful())
{
// use response data and do some fancy stuff :)
}
else
{
// parse the response body …
val error = ErrorUtils.parseError(response)
// … and use it to show error information
// … or just log the issue like we’re doing :)
Log.d("error message", error.message())
}
}
fun onFailure(call:Call<User>, t:Throwable) {
// there is more than just a failing request (like: no internet connection)
}
})
The complete example with a video is here retrofit-2-error-handling.
I am using Retrofit. Using Kotlin. I need to know the resonse status code. Like is it 200 or 500. How can I get it from the response ?
My Api class:
interface Api {
#POST("user/code/check")
fun checkSmsCode(#Body body: CheckCodeBody): Single<Response<Void>> }
This is how I am calling Api. But note that SERVE DOES NOT RETURN CODE FIELD IN RESPONSE BODY!
api.checkSmsCode(
CheckCodeBody(
code = code
)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
//HOW TO CHECK STATUS RESPONSE STATUS CODE HERE???
},
{ e ->
when (e) {
is IOException -> view?.showNoNetworkAlert()
else -> view?.invalidCodeError()
}
}
).also {}
As I understood, in Java it was a easy peasy thing.
You just use response.code() or something similar and that's it. But how to achieve it in Kotlin?
so your on response should look something like this
override fun onResponse(call: Call<MyModel>?, response: Response<MyModel>?) {
//
}
})
then inside that you should just to able to do
override fun onResponse(call: Call<MyModel>?, response: Response<MyModel>?) {
response.code()
}
})
is this what your talking about?
yo need to use it
interface OnlineStoreService{
#Headers("Content-Type: application/json","Connection: close")
#POST
fun getDevices(
#Url url: String,
#Header("Authorization") token: String,
#Body apiParams: APIParams
): Observable<OnlineStoresInfo>
}
.subscribe({ onlineStoresInfo -> // or it -> where "it" it's your object response, in this case is my class OnlineStoresInfo
loading.value = false
devices.value = onlineStoresInfo.devices
}, { throwable ->
Log.e(this.javaClass.simpleName, "Error getDevices ", throwable)
loading.value = false
error.value = context.getString(R.string.error_information_default_html)
})
.subscribe({ it ->
// code
}, { throwable ->
//code
})
If you haven't configure your retrofit request method to return a Response<*> you won't be able to have the response code. Example:
interface SomeApi{
#POST("user/code/check")
fun checkSmsCode(#Body body: CheckCodeBody): Single<Response<String>>
}
And after you finish your request:
.subscribe({
//access response code here like : it.code()
//and you can access the response.body() for your data
//also you can ask if that response.isSuccessful
})
I have REST POST call create order as Observable<OrderResponse>, when order create call is successful everything is fine, but then server returns error I get com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 2 path $ because Gson does not know how to handle json that have different fields than my response model
Server error response:
[{
"code": 99,
"message": "Please check your request"
}]
OrderResponse
data class OrderResponse(
#Expose
var orderId: String,
#Expose
var redirectUrl: String,
#Expose
var validUntil: Long
)
RxJava subscription
repository.requestCreateNewOrder(newOrder)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<OrderResponse> {
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
compositeDisposable.add(d)
}
override fun onNext(t: OrderResponse) {
}
override fun onError(t: Throwable) {
//HERE I GET JsonSyntaxException
}
})
Retrofit Service
interface OrderService {
#Headers(
"Content-Type: application/x-www-form-urlencoded ",
"Connection: Keep-Alive",
"Accept-Encoding: application/json",
"User-Agent: Fiddler"
)
#FormUrlEncoded
#POST("/createOrder")
fun createOrder(#Field("orderId") orderId: String,
#Field("payCurrency") payCurrency: String,
#Field("payAmount") payAmount: Double,
#Header("Content-Length") length: Int): Observable<OrderResponse>}
Anyone have any suggestions for how to pass retrofit or gson the error model to know how to handle it
As you are using GSON to parse the JSON.
Your JSON sucessfull response will be something like
{
"orderId": 1,
"redirectUrl": "url",
"validUntil": 12414194
}
while for error response your JSON response start with Array.
[{
"code": 99,
"message": "Please check your request"
}]
So tell to server guy to not add the error response in Array [].
If you are getting the response as Array then you have to use list.
repository.requestCreateNewOrder(newOrder)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<List<OrderResponse>> {
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
compositeDisposable.add(d)
}
override fun onNext(t: OrderResponse) {
}
override fun onError(t: Throwable) {
//HERE I GET JsonSyntaxException
}
})
In Observer response you have to add as List if your JSON response start with Array like error.
Solution : Correct it from backend for not adding Error Response in ARRAY.
As per your code you are only handling the success response
but you need to handle your error response too for this you need to ensure that your API should send you error codes different then 200 (success response generally greater then 200 and less then 300 i.e code >= 200 && code < 300 ) because retrofit consider 200-299 as success
You can achieve this simply by changing your observable return type to
Observable<Response< OrderResponse>>
and after receiving response from server simply check
if (orderResponse.isSuccessful()) {
//here you can handle success by using orderResponse.getbody()
} else {
// here you can display error message and if you further want
// to parse error response from server then use below function
errorOrderResponseHandling(orderResponse);
}
you want to further parse response into error model(in this example OrderAPIError is model class for error response) then below is the function
private void errorOrderResponseHandling(Response<OrderResponse> orderResponse) {
OrderAPIError orderAPIError = null;
try {
orderAPIError = new Gson().fromJson(String.valueOf(new
JSONObject(orderResponse.errorBody().string())), OrderAPIError.class);
// further orderAPIError object you can fetch server message and display
it to user as per your requirement
} catch (JSONException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
Above example is in android not in kotlin but you can get idea and use function accordingly in kotlin
I am using Retrofit2 with RxJava. So my call looks something like
subscriptions.add(authenticateUser(mReq, refreshRequest)
.observeOn(Schedulers.io())
.subscribeOn(Schedulers.io())
.subscribe(authResponseModel -> {
processResponse(authResponseModel, userName, encryptedPass);
}, throwable ->
{
LOGW(TAG, throwable.getMessage());
}));
It's an authentication api. So when the api call fails, I get a response from the server like
{"messages":["Invalid username or password "]}
along with 400 Bad Request
I get 400 Bad Request in the throwable object as expected. But I want to receive the message thrown by the server. I am unable to figure out how to go about doing it.
Can someone help out.
if(throwable instanceof HttpException) {
//we have a HTTP exception (HTTP status code is not 200-300)
Converter<ResponseBody, Error> errorConverter =
retrofit.responseBodyConverter(Error.class, new Annotation[0]);
//maybe check if ((HttpException) throwable).code() == 400 ??
Error error = errorConverter.convert(((HttpException) throwable).response().errorBody());
}
Assuming you are using Gson:
public class Error {
public List<String> messages;
}
the content of messages should be a list of error messages. In your example
messages.get(0) would be: Invalid username or password