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