Retrofit 2 - Элегантный способ добавления заголовков на уровне api

My 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, должен ли этот конкретный запрос иметь определенный заголовок.

Любая идея, как я могу сделать эту работу?
Я предпочитаю универсальное решение, а не только для токена аутентификации, но также приветствуется конкретное решение. Благодаря

Ответ 1

Я придумал очень простой и элегантный (на мой взгляд) решение моей проблемы и, возможно, для других сценариев.

Я использую аннотацию 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);
    }
});

Ответ 2

Возможно, вы можете сделать это, создав другой способ → .

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

если вы хотите вызвать api без заголовка auth, вы можете просто вызвать метод createService:

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

И используйте метод createServiceWithAuth, если вы хотите вызвать api с аутентификацией:

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