使用Retrofit进行冗余请求

时间:2018-01-29 16:30:26

标签: android retrofit2 okhttp3

我需要在我的应用程序中构建冗余,如果服务器关闭,它将在第一次请求失败时尝试备份冗余服务器。

除了做

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中有更简单的方法来处理它吗?

1 个答案:

答案 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

相关问题