2016-06-10 35 views
11

Moje Retrofit 2 (obecnie 2.0.2) klient musi dodać niestandardowe nagłówki do żądań.Retrofit 2 - Elegancki sposób dodawania nagłówków na poziomie api

Używam Interceptor dodać te nagłówki do wszystkich żądań:

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(); 

Niektóre nagłówki zawsze chcę dodać, ale niektóre nagłówki tylko trzeba dodać na podstawie wymagań zawartych w tym konkretnym punkcie końcowym, na przykład, czy użytkownik musi być uwierzytelniony, czy nie.

Chciałbym mieć możliwość kontrolowania, że ​​na poziomie API, na przykład za pomocą adnotacji, coś jak:

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

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

Wysyłając prośbę o register nie ma potrzeby, aby dodać token uwierzytelniający , ale żądania, którym brakuje adnotacji @NO_AUTH, będą miały nagłówek tokenu.

Z tego co rozumiem, Retrofit 2 nie obsługuje adnotacji niestandardowych i chociaż znalazłem to obejście dla Custom Annotations with Retrofit 2, wydaje się, że jest za dużo.

chciałbym uniknąć konieczności przechodzenia tych nagłówków na żądanie, takich jak:

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); 
} 

To po prostu czuje się zbędny robić to za każdym razem gdy wywołujemy metodę zamiast robić to w przechwytujących (od Mam statyczny dostęp do wartości nagłówka).
Po prostu muszę wiedzieć w mojej implementacji Interceptor.intercept, czy to konkretne żądanie powinno mieć określony nagłówek (y).

Masz pomysł, jak to zrobić?
Preferuję rozwiązanie ogólne, a nie tylko w przypadku tokena uwierzytelniającego, ale konkretne rozwiązanie również jest mile widziane. Dzięki

Odpowiedz

22

Wymyśliłem bardzo proste i eleganckie (moim zdaniem) rozwiązanie mojego problemu i prawdopodobnie dla innych scenariuszy.

Używam adnotacji Headers do przekazywania moich adnotacji niestandardowych, a ponieważ OkHttp wymaga, aby stosowały się do formatu Name: Value, zdecydowałem, że mój format będzie: @: ANNOTATION_NAME.

Więc zasadniczo:

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

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

Wtedy mogę przechwycić wniosek, sprawdzić, czy mam adnotację z nazwą @. Jeśli tak, otrzymuję wartość i usuwam nagłówek z żądania.
To działa dobrze, nawet jeśli chcesz mieć więcej niż jeden „niestandardowe adnotacji”:

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

Oto jak wydobyć wszystkie te „niestandardowe adnotacji” i usunąć je z życzenie:

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); 
    } 
}); 
+0

Czy to oznacza, że ​​używasz nowego '' 'OkHttpClient''' dla każdego żądania? –

+1

@panduka No. To było tylko dla przykładu, możesz mieć jednego klienta. –

+0

To jest najczystsze rozwiązanie, dzięki za udostępnienie :) – MatPag

4

Może to zrobić, tworząc inną metodę fabryki obiektów Retrofit, taką jak ta.

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); 
    } 
} 

jeśli chcesz zadzwonić api bez nagłówka uwierzytelniania, można po prostu wywołać metodę CreateService:

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

I użyć metody createServiceWithAuth jeśli chcesz zadzwonić api z uwierzytelnianiem:

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

Dzięki, to jest dobre rozwiązanie, ale wymaga ono zgrupowania punktów końcowych w różnych klasach w zależności od tego, czy żądania muszą być uwierzytelnione, a to nie jest bardzo wygodne. –