如何使用 Retrofit 2 处理空响应体?

Posted

技术标签:

【中文标题】如何使用 Retrofit 2 处理空响应体?【英文标题】:How can I handle empty response body with Retrofit 2? 【发布时间】:2016-01-18 14:37:30 【问题描述】:

最近我开始使用 Retrofit 2,但遇到了解析空响应正文的问题。我有一个服务器,它只响应 http 代码,响应正文中没有任何内容。

如何仅处理有关服务器响应的元信息(标头、状态代码等)?

【问题讨论】:

【参考方案1】:

编辑:

正如杰克·沃顿所指出的,

@GET("/path/to/get")
Call<Void> getMyData(/* your args here */);

与我原来的回答相比,这是最好的方法--

您可以只返回一个ResponseBody,它将绕过解析响应。

@GET("/path/to/get")
Call<ResponseBody> getMyData(/* your args here */);

然后在你的电话中,

Call<ResponseBody> dataCall = myApi.getMyData();
dataCall.enqueue(new Callback<ResponseBody>() 
    @Override
    public void onResponse(Response<ResponseBody> response) 
        // use response.code, response.headers, etc.
    

    @Override
    public void onFailure(Throwable t) 
        // handle failure
    
);

【讨论】:

甚至更好:使用Void,它不仅具有更好的语义,而且(稍微)在空情况下效率更高,在非空情况下效率更高(当您不在乎时)关于身体)。 @JakeWharton 这是很好的行为。感谢您指出。答案已更新。 很好的答案。不使用 Void 的一个原因是,如果您的资源仅在请求不成功时返回正文,并且您希望将 errorBody ResponseBody 转换为某种特定或常见类型。 @JakeWharton 使用Void 的好建议。在 Kotlin 代码中使用 Unit 是否会提供与 Java 中的 Void 相同的好处进行改造? @akshay-chordiya 我刚刚检查过,Kotlin 中的 Unit 不起作用,但是 Void 起作用。我假设某处有硬编码检查。【参考方案2】:

如果你使用 RxJava,那么这种情况下最好使用Completable

表示没有任何值但仅指示完成或异常的延迟计算。该类遵循与 Reactive-Streams 类似的事件模式:onSubscribe (onError|onComplete)?

http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Completable.html

在接受的答案中:

@GET("/path/to/get")
Observable<Response<Void>> getMyData(/* your args here */);

如果端点返回失败响应码,它仍然会在onNext中,您必须自己检查响应码。

但是,如果您使用Completable

@GET("/path/to/get")
Completable getMyData(/* your args here */);

您将只有onCompleteonError。 如果响应代码成功,它将触发onComplete,否则将触发onError

【讨论】:

在这种情况下,onError Throwable 参数将包含什么?我觉得这个更干净,但我们仍然经常需要查看响应代码和正文是否有故障。【参考方案3】:

如果您使用的是 rxjava,请使用类似:

@GET("/path/to/get")
Observable<Response<Void>> getMyData(/* your args here */);

【讨论】:

这就是我发现的!谢谢! 我已经将 ResposeBody 与 RxJava2 和 Retrofit2 一起用于 PUT REST 请求。效果很好 我们有一个端点 API,成功时返回空正文,错误时返回 json 正文。如果使用 Response,我该如何处理错误情况? 你在这里使用哪个响应类?改造还是 OKHttps? 如果您对异常进行错误处理,这不是一个好的选择。使用这种方法不会出现异常,而是使用 JSON 作为错误响应【参考方案4】:

对于 kotlin,使用返回类型 Call&lt;Void&gt; 仍然会抛出 IllegalArgumentException: Unable to create converter for retrofit2.Call&lt;java.lang.Void&gt;

使用响应而不是调用解决了问题

@DELETE("user/data")
suspend fun deleteUserData(): Response<Void>

【讨论】:

【参考方案5】:

这是我将它与 Rx2 和 Retrofit2 一起使用的方式,以及 PUT REST 请求: 我的请求有一个 json 正文,但只是带有空正文的 http 响应代码。

Api 客户端:

public class ApiClient 
public static final String TAG = ApiClient.class.getSimpleName();


private DevicesEndpoint apiEndpointInterface;

public DevicesEndpoint getApiService() 


    Gson gson = new GsonBuilder()
            .setLenient()
            .create();


    OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
    HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
    logging.setLevel(HttpLoggingInterceptor.Level.BODY);
    okHttpClientBuilder.addInterceptor(logging);

    OkHttpClient okHttpClient = okHttpClientBuilder.build();

    apiEndpointInterface = new Retrofit.Builder()
            .baseUrl(ApiContract.DEVICES_REST_URL)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build()
            .create(DevicesEndpoint.class);

    return apiEndpointInterface;


界面:

public interface DevicesEndpoint 
 @Headers("Content-Type: application/json")
 @PUT(ApiContract.DEVICES_ENDPOINT)
 Observable<ResponseBody> sendDeviceDetails(@Body Device device);

然后使用它:

    private void sendDeviceId(Device device)

    ApiClient client = new ApiClient();
    DevicesEndpoint apiService = client.getApiService();
    Observable<ResponseBody> call = apiService.sendDeviceDetails(device);

    Log.i(TAG, "sendDeviceId: about to send device ID");
    call.subscribeOn(Schedulers.io()).observeOn(androidSchedulers.mainThread()).subscribe(new Observer<ResponseBody>() 
        @Override
        public void onSubscribe(Disposable disposable) 
        

        @Override
        public void onNext(ResponseBody body) 
            Log.i(TAG, "onNext");
        

        @Override
        public void onError(Throwable t) 
            Log.e(TAG, "onError: ", t);

        

        @Override
        public void onComplete() 
            Log.i(TAG, "onCompleted: sent device ID done");
        
    );


【讨论】:

【参考方案6】:

你可以试试这个

Retrofit retrofit = new Retrofit.Builder().baseUrl(baseUrl)
                .addConverterFactory(new NullOnEmptyConverterFactory())
.client(okHttpClient).build();
 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);
        ;
    

【讨论】:

以上是关于如何使用 Retrofit 2 处理空响应体?的主要内容,如果未能解决你的问题,请参考以下文章

改造 2 - 空响应体

如何使用 Retrofit2 解决空 Web 服务响应?

如何使用 Retrofit 2 处理动态 JSON?

如何使用 Retrofit 2 和 RxJava 处理分页

Retrofit 响应体无body时解析EOFException

如何使用 Kotlin Coroutines 在 Retrofit 中处理 204 响应?