【问题标题】:Retrofit 2 - Elegant way of adding headers in the api levelRetrofit 2 - 在 api 级别添加标题的优雅方式
【发布时间】:2016-10-11 23:16:00
【问题描述】:

我的 Retrofit 2(当前为2.0.2)客户端需要向请求添加自定义标头。

我正在使用Interceptor 将这些标头添加到所有请求中:

OkHttpClient httpClient = new OkHttpClient();
httpClient.networkInterceptors().add(new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        final Request request = chain.request().newBuilder()
                .addHeader("CUSTOM_HEADER_NAME_1", "CUSTOM_HEADER_VALUE_1")
                .addHeader("CUSTOM_HEADER_NAME_2", "CUSTOM_HEADER_VALUE_2")
                ...
                .addHeader("CUSTOM_HEADER_NAME_N", "CUSTOM_HEADER_VALUE_N")
                .build();

        return chain.proceed(request);
    }
});


Retrofit retrofitClient = new Retrofit.Builder()
        .baseUrl(baseUrl)
        .client(httpClient)
        .build();

我总是想添加一些标头,但我只需要根据特定端点的要求添加一些标头,例如是否需要对用户进行身份验证。

我希望能够在 api 级别控制它,例如使用注释,例如:

public interface MyApi {
    @NO_AUTH
    @POST("register")
    Call<RegisterResponse> register(@Body RegisterRequest data);

    @GET("user/{userId}")
    Call<GetUserResponse> getUser(@Path("userId") String userId);
}

register发送请求时,无需添加身份验证令牌,但缺少@NO_AUTH注释的请求将具有令牌头。

据我了解,Retrofit 2 不支持自定义注释,虽然我发现 Custom Annotations with Retrofit 2 的这种解决方法,但它似乎有点太多了。

我想避免每次请求都传递这些标头,例如:

public interface MyApi {
    @POST("register")
    Call<RegisterResponse> register(@Body RegisterRequest data);

    @GET("user/{userId}")
    Call<GetUserResponse> getUser(@Header("AuthToken") String token, @Path("userId") String userId);
}

每次我调用方法而不是在拦截器中执行它只是感觉多余(因为我可以静态访问标头值)。
我只是想知道在我的Interceptor.intercept 实现中这个特定的请求是否应该有一个特定的标头。

知道我怎样才能完成这项工作吗?
我更喜欢通用解决方案,而不仅仅是针对 auth 令牌案例,也欢迎使用特定解决方案。 谢谢

【问题讨论】:

    标签: java header annotations retrofit retrofit2


    【解决方案1】:

    你也可以在this article中做类似的事情

    我添加了一个小的额外扩展来缩短所有内容

    fun Request.isAnnotationPresent(annotation: Class<out Annotation>) =
        tag(Invocation::class.java)?.method()?.isAnnotationPresent(annotation) ?: false
    
    private class CustomInterceptor(private val account: Account): Interceptor {
    
        override fun intercept(chain: Interceptor.Chain): Response {chain.request().tag(Invocation::class.java)?.method()
            if(chain.request().isAnnotationPresent(CustomAnnotation)) {
                // Do your stuff
            }
            return chain.proceed(chain.request())
        }
    }
    

    【讨论】:

      【解决方案2】:

      我想出了一个非常简单和优雅(在我看来)的解决方案来解决我的问题,并且可能适用于其他情况。

      我使用Headers 注释来传递我的自定义注释,并且由于OkHttp 要求它们遵循Name: Value 格式,我决定我的格式为:@: ANNOTATION_NAME

      所以基本上:

      public interface MyApi {
          @POST("register")
          @HEADERS("@: NoAuth")
          Call<RegisterResponse> register(@Body RegisterRequest data);
      
          @GET("user/{userId}")
          Call<GetUserResponse> getUser(@Path("userId") String userId);
      }
      

      然后我可以拦截请求,检查我是否有一个名为@的注解。如果是这样,我会获取该值并从请求中删除标头。
      即使您想拥有多个“自定义注释”,这也很有效:

      @HEADERS({
          "@: NoAuth",
          "@: LogResponseCode"
      })
      

      以下是如何提取所有这些“自定义注释”并将它们从请求中删除:

      new OkHttpClient.Builder().addNetworkInterceptor(new Interceptor() {
          @Override
          public okhttp3.Response intercept(Chain chain) throws IOException {
              Request request = chain.request();
      
              List<String> customAnnotations = request.headers().values("@");
      
              // do something with the "custom annotations"
      
              request = request.newBuilder().removeHeader("@").build();
              return chain.proceed(request);
          }
      });
      

      【讨论】:

      • 这是否意味着您对每个请求都使用新的OkHttpClient
      • @panduka 不。这只是一个例子,你可以有一个客户。
      • 这实际上是最干净的解决方案,感谢分享:)
      • @NitzanTomer 精心设计的解决方案。它运行良好,但我想知道为什么HttpLoggingInterceptor 仍然在标题中显示@: NoAuth。是不是因为在被移除之前被拦截了?
      • @Poorya 你能在这个后面加上HttpLoggingInterceptor 还是有问题?
      【解决方案3】:

      也许你可以通过像这样创建不同的 Retrofit 对象工厂方法来做到这一点。

      public class RestClient {
          public static <S> S createService(Class<S> serviceClass) {
              OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
              OkHttpClient client = httpClient.build();
      
              Retrofit retrofit = new Retrofit.Builder().baseUrl(APIConfig.BASE_URL)
                      .client(client)
                      .build();
              return retrofit.create(serviceClass);
          }
      
          public static <S> S createServiceWithAuth(Class<S> serviceClass) {
              Interceptor interceptor = new Interceptor() {
                  @Override
                  public Response intercept(Chain chain) throws IOException {
                      final Request request = chain.request().newBuilder()
                              .addHeader("CUSTOM_HEADER_NAME_1", "CUSTOM_HEADER_VALUE_1")
                              .addHeader("CUSTOM_HEADER_NAME_2", "CUSTOM_HEADER_VALUE_2")
                              .build();
      
                      return chain.proceed(request);
                  }
              };
              OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
              httpClient.addInterceptor(interceptor);
              OkHttpClient client = httpClient.build();
      
              Retrofit retrofit = new Retrofit.Builder().baseUrl(APIConfig.BASE_URL)
                      .client(client)
                      .build();
              return retrofit.create(serviceClass);
          }
      }
      

      如果你想调用没有header auth的api,你可以调用createService方法:

      YourApi api = RestClient.createService(YourApi.class);
      

      如果你想调用带有身份验证的api,请使用createServiceWithAuth方法:

      YourApiWithAuth api = RestClient.createServiceWithAuth(YourApiWithAuth.class);
      

      【讨论】:

      • 谢谢,这是一个很好的解决方案,但它需要我根据请求是否需要进行身份验证将端点分组到不同的类中,这不是很方便。
      • 祝你好运,每次需要时都实例化资源昂贵的改造。
      • 这是正确的解决方案。已认证和未认证的请求应尽可能分开。
      猜你喜欢
      • 1970-01-01
      • 2016-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-25
      • 1970-01-01
      相关资源
      最近更新 更多