-
Notifications
You must be signed in to change notification settings - Fork 7.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handle Empty Body #1554
Comments
Are you getting the same error when you don´t set a converter factory? |
I didn't test without the converter. I guess it will not fail because |
What behavior do you expect when the body is empty then? You are telling Retrofit to deserialize the response as a certain object and Retrofit tells Gson to parse the stream into that type and the type is empty. This behavior already seems very reasonable to me. |
Just return an "empty" pojo response (Like it was until now)... all fields null? It's a real problem...for example I didn't write the server side, just the Android App...The server side returns 200 with empty body...but there are times that the same call will return a 200 with a body message. I have nothing to do in situations like that |
An empty pojo is You can write a delegating converter that does this, the behavior will not be added to Retrofit by default. class NullOnEmptyConverterFactory implements Converter.Factory {
@Override public Converter<ResponseBody, ?> responseBody(Type type, Annotation[] annotations, Retrofit retrofit) {
final Converter<ResponseBody, ?> delegate = retrofit.nextResponseBodyConverter(this, type, annotations);
return new Converter<>() {
@Override public void convert(ResponseBody body) {
if (body.contentLength() == 0) return null;
return delegate.convert(body);
}
};
}
} Retrofit retrofit = new Retrofit.Builder()
.endpoint(..)
.addConverterFactory(new NullOnEmptyConverterFactory())
.addConverterFactory(GsonConverterFactory.create())
.build(); |
I had to implement this for a requirement from my services team. Here is the updated code I used for release version of Retrofit 2.0 that seems to be working in case anyone else comes across the issue. public class NullOnEmptyConverterFactory extends Converter.Factory {
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
final Converter<ResponseBody, ?> delegate = retrofit.nextResponseBodyConverter(this, type, annotations);
return new Converter<ResponseBody, Object>() {
@Override
public Object convert(ResponseBody body) throws IOException {
if (body.contentLength() == 0) return null;
return delegate.convert(body); }
};
}
} |
body.contentLength() always returns -1 on me. What to do? |
Just read it. That means unknown content length. On Wed, May 11, 2016, 11:51 PM nAkhmedov [email protected] wrote:
|
05-12 11:57:35.040 12039-13092/com.sms.sendsms D/OkHttp: <-- 200 OK http://www.yesplease.co.il/app/login.asp?username=zolim2&password=123456g (554ms) |
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();//If need to logging, just uncomment |
I add the NullOnEmptyConverterFactory to the retrofit, and still gets the error.Then I find the order is important. Retrofit retrofit = new Retrofit.Builder()
.endpoint(..)
.addConverterFactory(new NullOnEmptyConverterFactory()) //this should come first
.addConverterFactory(GsonConverterFactory.create())
.build(); |
help for Kotlin devs val nullOnEmptyConverterFactory = object : Converter.Factory() {
fun converterFactory() = this
override fun responseBodyConverter(type: Type, annotations: Array<out Annotation>, retrofit: Retrofit) = object : Converter<ResponseBody, Any?> {
val nextResponseBodyConverter = retrofit.nextResponseBodyConverter<Any?>(converterFactory(), type, annotations)
override fun convert(value: ResponseBody) = if (value.contentLength() != 0L) nextResponseBodyConverter.convert(value) else null
}
} |
@jacklt That is an inefficient solution. You have to look up the converter for every response. Look it up once in the factory so that it can be used over and over by the returned converter. |
@JakeWharton Why? I create only one |
It calls |
@jacklt You want to cache the result of
|
Ok, thanks! That's the full version val nullOnEmptyConverterFactory = object : Converter.Factory() {
fun converterFactory() = this
override fun responseBodyConverter(type: Type, annotations: Array<out Annotation>, retrofit: Retrofit) = object : Converter<ResponseBody, Any?> {
val nextResponseBodyConverter = retrofit.nextResponseBodyConverter<Any?>(converterFactory(), type, annotations)
override fun convert(value: ResponseBody) = if (value.contentLength() != 0L) nextResponseBodyConverter.convert(value) else null
}
} |
In Gson's
So could it be possible that Any way, the |
No, we won't be implementing that. An empty body is not valid JSON. On Sun, Aug 21, 2016 at 12:58 AM Piasy [email protected] wrote:
|
@JakeWharton will it deal with empty arrays like "array":[] ? |
That's up to the serialization library you are using. |
I've got a good way to handle this, just want to share with you guys :) I create an public class EmptyJsonLenientConverterFactory extends Converter.Factory {
private final GsonConverterFactory mGsonConverterFactory;
public EmptyJsonLenientConverterFactory(GsonConverterFactory gsonConverterFactory) {
mGsonConverterFactory = gsonConverterFactory;
}
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
return mGsonConverterFactory.requestBodyConverter(type, parameterAnnotations,
methodAnnotations, retrofit);
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
final Converter<ResponseBody, ?> delegateConverter =
mGsonConverterFactory.responseBodyConverter(type, annotations, retrofit);
return value -> {
try {
return delegateConverter.convert(value);
} catch (EOFException e) {
// just return null
return null;
}
};
}
} And besides, my server return api error in HTTP 200 response, so I also create a public class YLApiErrorAwareConverterFactory extends Converter.Factory {
private final EmptyJsonLenientConverterFactory mEmptyJsonLenientConverterFactory;
public YLApiErrorAwareConverterFactory(
EmptyJsonLenientConverterFactory emptyJsonLenientConverterFactory) {
mEmptyJsonLenientConverterFactory = emptyJsonLenientConverterFactory;
}
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
return mEmptyJsonLenientConverterFactory.requestBodyConverter(type, parameterAnnotations,
methodAnnotations, retrofit);
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
final Converter<ResponseBody, ?> apiErrorConverter =
mEmptyJsonLenientConverterFactory.responseBodyConverter(YLApiError.class,
annotations, retrofit);
final Converter<ResponseBody, ?> delegateConverter =
mEmptyJsonLenientConverterFactory.responseBodyConverter(type, annotations,
retrofit);
return value -> {
// read them all, then create a new ResponseBody for ApiError
// because the response body is wrapped, we can't clone the ResponseBody correctly
MediaType mediaType = value.contentType();
String stringBody = value.string();
try {
Object apiError = apiErrorConverter
.convert(ResponseBody.create(mediaType, stringBody));
if (apiError instanceof YLApiError && ((YLApiError) apiError).isApiError()) {
throw (YLApiError) apiError;
}
} catch (JsonSyntaxException notApiError) {
}
// then create a new ResponseBody for normal body
return delegateConverter.convert(ResponseBody.create(mediaType, stringBody));
};
}
} And to wire them up: Retrofit retrofit = new Retrofit.Builder().client(okHttpClient)
.baseUrl(URL)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(new YLApiErrorAwareConverterFactory(
new EmptyJsonLenientConverterFactory(GsonConverterFactory.create(gson))))
.build(); |
package com.dooioo.core.network; import java.io.IOException; import okhttp3.ResponseBody; /**
|
|
The server I am talking to (which I do not control) does not return a valid content-length so solutions that rely on it do not work for me. Furthermore I am using kotlin and using moshi. This is the only solution I could get working. I realize this has a serious error that swallows class NullMoshiConverterFactory(val moshi: Moshi) : Converter.Factory() {
val factory: MoshiConverterFactory
init {
factory = MoshiConverterFactory.create(moshi)
}
override fun requestBodyConverter(type: Type?, parameterAnnotations: Array<out Annotation>?, methodAnnotations: Array<out Annotation>?, retrofit: Retrofit?): Converter<*, RequestBody> {
return factory.requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit)
}
override fun responseBodyConverter(type: Type, annotations: Array<out Annotation>, retrofit: Retrofit): Converter<ResponseBody, *>? {
return NullConverterFactory(factory.responseBodyConverter(type, annotations, retrofit))
}
class NullConverterFactory(val wrapped: Converter<ResponseBody, *>) : Converter<ResponseBody, Any> {
override fun convert(value: ResponseBody?): Any? {
try {
return wrapped.convert(value)
} catch (ex: EOFException) {
return null
}
}
}
} |
E/UncaughtException: java.lang.IllegalArgumentException: Expected receiver of type com.XXXX.models.SuccessResponse, but got retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall |
@nAkhmedov I met the same situation as you, how do you deal with it? |
@SugerQ i tottaly forget what was the reason. Sorry |
Blast from the past, but here is a fully anonymous impl:
|
Sadly, here's another 'me too'. I've tried each and every one of these approaches, without success. I'm creating my builder like this:
This is in a static method, so I'm creating my NullConverter like this:
Now, in every call where there's a body, I'm getting this in the log:
So, it would seem as if the Null Converter is at least trying to do its job. However, on the call that is causing me difficulties (200 response, but empty body), I don't get those lines in the output. All I can imagine is that the EOFException is being generated before the converter is getting a chance to protect us from the EOFException. So, even though I'm getting a 200 from the server, my code is in the error handler branch. Yes, for pedants, I understand this is not "good", "valid" or "proper" HTML, and that the server needs to change. To that I would say that the world is not perfect. It shouldn't be so ridiculously hard to handle such a simple situation. -Ken |
In my case I'm receiving Response body "[text={"errorCode":"username_in_use","messages":null}]" but still while converting response it throwing EOFException. Any suggestion !!
|
what's your |
Please reopen it, it is not working. When the server has no body and code is 4xx. |
A little help would be much appreciated here. Unfortunately I can't change the API I try to consume and one particular endpoint returns 202 - without any payload, no "{}", nothing - until the data is ready, then it'll return with a 200 and the proper payload. When I to parse the 202 response it throws the exception. Any idea how to solve this? Is there a way to handle this - to handle the null response part and the valid payload too? This is the converter:
This is the error message I get when I'm using HttpLoggingInterceptor to monitor the calls:
And this is the error message I get when I don't use the LogginInterceptor:
|
Not sure if this helps, but I was able to use |
Thanks @andrewdittmer! After a few hours of extensive digging around on SO and in github tickets I found out that my issue is caused by the backend. The API response lacks proper header data, and that's the reason I get the exception. The thread that helped me figure it out: square/okhttp#3646 |
Just a heads up, I was facing this behaviour (null body exception) even with the solutions above. Then, I updated my API to return 200 instead of 204 (no content) and it worked. Note: As I'm using RxJava, I needed to use |
I am also facing the same issue. means "Empty Body" and content length is -1, so after adding NullOnEmptyConverterFactory also getting the issue. I think we should not add contentLength != -1 condition inside NullOnEmptyConverterFactory because square/okhttp#3309 Is there any solution? |
Here is my solution in Kotlin (I found some of the above answers a bit confusing): class NullOnEmptyConverterFactory @Inject constructor() : Converter.Factory() {
override fun responseBodyConverter(
type: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
) = Converter<ResponseBody, Any?> {
if (it.contentLength() != 0L) retrofit.nextResponseBodyConverter<Any?>(
this,
type,
annotations
).convert(it) else null
}
} |
Here is my solution: class NullOnEmptyConverterFactory extends Converter.Factory {
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
final Converter<ResponseBody, ?> delegate = retrofit.nextResponseBodyConverter(this, type, annotations);
return (Converter<ResponseBody, Object>) body -> {
if (body.source().exhausted()) return null;
return delegate.convert(body);
};
}
} |
@crisu83 @npwork did you test it? I've tested in retrofit v2.9.0 and contentLegth() is always -1, |
Any reasons why the use of |
Thanks! This is working.. |
After updating to retrofit beta-3 I'm getting the Exception (Because of an empty body)
I know that It's possible to solve this issue Using
Call<Void>
but is there any other way to enforceOkHttp
orGson
To accept Empty body?Response Log:
The text was updated successfully, but these errors were encountered: