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

Мой Ретрофит 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 не поддерживает пользовательские аннотации, и пока я нашел это обходное решение для пользовательские аннотации с дооснащением 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, но и конкретное решение приветствуется. Спасибо

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

возможно, вы можете сделать это, создав другой модифицированный заводской метод объекта, подобный этому.

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 без аутентификации заголовка, вы можете просто вызвать метод createService:

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

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

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