web-dev-qa-db-fra.com

Retrofit 2 - Une manière élégante d'ajouter des en-têtes au niveau de l'API

My Retrofit 2 (2.0.2 actuellement) le client doit ajouter des en-têtes personnalisés aux demandes.

J'utilise un Interceptor pour ajouter ces en-têtes à toutes les demandes:

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

Certains en-têtes que je veux toujours ajouter, mais certains en-têtes que je dois seulement ajouter en fonction des exigences de ce point de terminaison spécifique, par exemple si l'utilisateur doit être authentifié ou non.

J'aimerais pouvoir contrôler cela au niveau de l'API, par exemple en utilisant une annotation, quelque chose comme:

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

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

Lors de l'envoi d'une demande à register, il n'est pas nécessaire d'ajouter le jeton d'authentification, mais les demandes qui n'ont pas le @NO_AUTH l'annotation aura l'en-tête du jeton.

D'après ce que je comprends, Retrofit 2 ne prend pas en charge les annotations personnalisées, et même si j'ai trouvé cette solution de contournement pour Annotations personnalisées avec Retrofit 2 , cela semble un peu trop.

Je voudrais éviter d'avoir à transmettre ces en-têtes par demande, comme:

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

Cela me semble redondant de le faire à chaque fois que j'appelle la méthode au lieu de le faire dans l'intercepteur (car j'ai accès aux valeurs d'en-tête statiquement).
J'ai juste besoin de savoir dans mon Interceptor.intercept implémentation, que cette demande spécifique ait ou non un ou des en-têtes spécifiques.

Une idée de comment je peux faire fonctionner ça?
Je préfère une solution générique et pas seulement pour le cas du jeton d'authentification, mais une solution spécifique est également la bienvenue. Merci

15
Nitzan Tomer

J'ai trouvé une solution très simple et élégante (à mon avis) à mon problème, et probablement pour d'autres scénarios.

J'utilise l'annotation Headers pour transmettre mes annotations personnalisées et, depuis OkHttp, elles doivent suivre Name: Value format, j'ai décidé que mon format sera: @: ANNOTATION_NAME.

Donc en gros:

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

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

Ensuite, je peux intercepter la demande, vérifier si j'ai une annotation avec le nom @. Si c'est le cas, j'obtiens la valeur et supprime l'en-tête de la demande.
Cela fonctionne bien même si vous souhaitez avoir plus d'une "annotation personnalisée":

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

Voici comment extraire toutes ces "annotations personnalisées" et les supprimer de la demande:

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);
    }
});
28
Nitzan Tomer

Vous pouvez peut-être le faire en créant différentes méthodes de fabrique d'objets Retrofit comme celle-ci.

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

si vous voulez appeler api sans authentification d'en-tête, vous pouvez simplement appeler la méthode createService:

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

Et utilisez la méthode createServiceWithAuth si vous souhaitez appeler l'api avec authentification:

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