Retrofit 的冗余请求
Redundancy requests with Retrofit
我需要在我的应用程序中构建冗余,如果服务器出现故障,它将在第一个请求失败时尝试备份冗余服务器。
除了做
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 中有更简单的方法来处理这个问题吗?
我这里有一个 OkHttp
拦截器的选项。这个想法是,如果请求失败,您将替换 url 并再次执行请求。
以下是 OpenWeather Api 的 api 客户端。如果您想试用该示例,您需要注册并获得一个 api 密钥。它应该是免费的所以我希望这没问题。
我会 post 在这里提供完整的代码,然后引导您完成它。
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
。在这里,我复制正在发出的请求并更改 url.
更改url意味着更改主机:
HttpUrl replicaUrl = failedRequest.url()
.newBuilder()
.host(GOOD_HOST)
.build();
如果您只是在请求生成器中调用 url(<some url>)
,所有内容都会被替换。查询参数、协议等。通过这种方式,我们可以保留原始请求中的这些内容。
(OkHttp 提供了 newBuilder
方法,可以从当前对象复制数据,让你可以编辑你想要的东西。就像 kotlin 的 copy
一样。这就是为什么我们可以只需更改 url 并确保其他一切保持不变)
然后我使用 url 构建新请求并执行它:
okhttp3.Request request = failedRequest.newBuilder()
.url(replicaUrl)
.build();
return chain.proceed(request);
拦截器以链模式工作,这就是为什么调用 proceed 将调用链上的下一个拦截器。在这种情况下,我们只需要实际提出请求即可。
我没有复制整个天气资源,所以我只是使用了 id。我觉得这不是问题的重点
正如我之前所说,这意味着概念验证。正如您所注意到的,我正在 try-catch
执行调用,但在您的情况下,调用实际上可能成功执行,但 http 响应不是 2XX。 okhttp 响应对象具有帮助您检查响应是否成功的方法,即 - isSuccessful()。想法是一样的 - 建立一个新的请求,如果不成功则继续。
在此示例中,我没有费心处理副本中的任何错误。它们只会被转发给改装客户。
如您所见,retrofit 不知道响应来自何处。这可能好也可能不好。另外,两个服务器的响应正文必须相同,我猜是这样的。
最后,对于尴尬的 okhttp3.Response
名称间距,我深表歉意。我同时使用了 retrofit 和 okhttp 中的 Response
,因此必须避免名称冲突。
本示例使用的版本:Retrofit 2.3.0 和与之捆绑的 okhttp
我需要在我的应用程序中构建冗余,如果服务器出现故障,它将在第一个请求失败时尝试备份冗余服务器。
除了做
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 中有更简单的方法来处理这个问题吗?
我这里有一个 OkHttp
拦截器的选项。这个想法是,如果请求失败,您将替换 url 并再次执行请求。
以下是 OpenWeather Api 的 api 客户端。如果您想试用该示例,您需要注册并获得一个 api 密钥。它应该是免费的所以我希望这没问题。
我会 post 在这里提供完整的代码,然后引导您完成它。
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
。在这里,我复制正在发出的请求并更改 url.
更改url意味着更改主机:
HttpUrl replicaUrl = failedRequest.url()
.newBuilder()
.host(GOOD_HOST)
.build();
如果您只是在请求生成器中调用 url(<some url>)
,所有内容都会被替换。查询参数、协议等。通过这种方式,我们可以保留原始请求中的这些内容。
(OkHttp 提供了 newBuilder
方法,可以从当前对象复制数据,让你可以编辑你想要的东西。就像 kotlin 的 copy
一样。这就是为什么我们可以只需更改 url 并确保其他一切保持不变)
然后我使用 url 构建新请求并执行它:
okhttp3.Request request = failedRequest.newBuilder()
.url(replicaUrl)
.build();
return chain.proceed(request);
拦截器以链模式工作,这就是为什么调用 proceed 将调用链上的下一个拦截器。在这种情况下,我们只需要实际提出请求即可。
我没有复制整个天气资源,所以我只是使用了 id。我觉得这不是问题的重点
正如我之前所说,这意味着概念验证。正如您所注意到的,我正在 try-catch
执行调用,但在您的情况下,调用实际上可能成功执行,但 http 响应不是 2XX。 okhttp 响应对象具有帮助您检查响应是否成功的方法,即 - isSuccessful()。想法是一样的 - 建立一个新的请求,如果不成功则继续。
在此示例中,我没有费心处理副本中的任何错误。它们只会被转发给改装客户。
如您所见,retrofit 不知道响应来自何处。这可能好也可能不好。另外,两个服务器的响应正文必须相同,我猜是这样的。
最后,对于尴尬的 okhttp3.Response
名称间距,我深表歉意。我同时使用了 retrofit 和 okhttp 中的 Response
,因此必须避免名称冲突。
本示例使用的版本:Retrofit 2.3.0 和与之捆绑的 okhttp