我需要在我的应用程序中构建冗余,如果服务器关闭,它将在第一次请求失败时尝试备份冗余服务器。
除了做
Call<LoginResult> loginCall = apiInterface.login(....);
loginCall.enqueue(new Callback<LoginResult>() {
@Override
public void onResponse(Call<LoginResult> call, Response<LoginResult> response) {
if(response.isSuccessful){
//do normal stuff
}else{
//try second url
}
}
@Override
public void onFailure(Call<LoginResult> call, Throwable t) {
//Try second url
}
}
我没有看到干净的方法来做到这一点。在错误块或非成功块中创建另一个改进请求会增加很多代码复杂性。
在Retrofit或OkHttp中有更简单的方法来处理它吗?
答案 0 :(得分:1)
我在这里有OkHttp
拦截器的选项。我们的想法是,如果请求失败,则替换url并再次执行请求。
以下是OpenWeather Api的api客户端。如果你想试试这个例子,你需要注册并获得一个api密钥。它应该是免费的,所以我希望这没关系。
我会在这里发布完整的代码,然后引导您完成它。
private final static String API_KEY = "<API KEY HERE>";
private static class Weather {
@SerializedName("id")
@Expose
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
private static final String GOOD_HOST = "api.openweathermap.org";
private static final String BAD_ENDPOINT = "https://api.aaaaaaaaaaa.org";
interface WeatherApiClient {
@GET("/data/2.5/weather")
Call<Weather> get(
@Query("q") String query,
@Query("appid") String apiKey);
}
private static class ReplicaServerInterceptor implements Interceptor {
@Override public okhttp3.Response intercept(Chain chain)
throws IOException {
try {
okhttp3.Response response = chain.proceed(chain.request());
return response;
} catch (IOException e) {
// Let's build a new request based on the old one
Request failedRequest = chain.request();
HttpUrl replicaUrl = failedRequest.url()
.newBuilder()
.host(GOOD_HOST)
.build();
okhttp3.Request request = failedRequest.newBuilder()
.url(replicaUrl)
.build();
return chain.proceed(request);
}
}
}
public static void main(String[] args) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new ReplicaServerInterceptor())
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BAD_ENDPOINT)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
WeatherApiClient weatherApiClient =
retrofit.create(WeatherApiClient.class);
weatherApiClient.get("Lisbon,pt", API_KEY)
.enqueue(new Callback<Weather>() {
@Override public void onResponse(
Call<Weather> call,
Response<Weather> response) {
// This might be null sometimes because
// the api is not super reliable, but I didn't
// add code for this
System.out.println(response.body().id);
}
@Override public void onFailure(
Call<Weather> call,
Throwable t) {
t.printStackTrace();
}
});
}
为了能够伪造服务器故障,我准备改造以调用不存在的URL - BAD_ENDPOINT
。这将触发拦截器内的catch子句。
拦截器本身显然是关键。它拦截来自改造的每个呼叫并执行呼叫。如果调用因服务器关闭而抛出错误,则会引发IOException
。在这里,我复制正在进行的请求并更改网址。
更改网址意味着更改主机:
HttpUrl replicaUrl = failedRequest.url()
.newBuilder()
.host(GOOD_HOST)
.build();
如果您只是在请求构建器中调用url(<some url>)
,则所有内容都会被替换。查询参数,协议等。这样,我们将这些保留在原始请求中。
(OkHttp提供了newBuilder
方法,这些方法可以复制当前对象中的数据,让您只需编辑自己想要的内容。就像kotlin的copy
一样。这就是为什么我们可以只需更改网址,并确保其他所有内容保持不变)
然后我使用url构建新请求并执行它:
okhttp3.Request request = failedRequest.newBuilder()
.url(replicaUrl)
.build();
return chain.proceed(request);
拦截器处理链模式,这就是为什么调用proceed会调用链上的下一个拦截器。在这种情况下,我们只需要实际提出请求。
我没有打扰复制整个天气资源,所以我只是使用了id。我认为这不是问题的主要焦点
正如我之前所说,这是一个概念证明。正如您注意到我try-catch
执行了呼叫,但在您的情况下,可能是呼叫实际上成功执行,但http响应不是2XX。 okhttp响应对象有一些方法可以帮助您检查响应是否成功,即isSuccessful()。这个想法是一样的 - 建立一个新的请求并继续进行,如果它没有成功。
在这个例子中,我没有打扰处理副本中的任何错误。他们只是转发给改造客户。
正如您所看到的那样,改造不知道响应的来源。这可能或不好。此外,两个服务器的响应主体需要相同,我猜是这样的。
最后,我很抱歉那里有一个尴尬的okhttp3.Response
名字间距。我正在使用改造中的Response
和okhttp,因此必须避免名称冲突。
此示例使用的版本:Retrofit 2.3.0和与之捆绑的okhttp