Retrofit 2 - Elegant way of adding headers in the api level

懵懂的女人 提交于 2019-12-20 09:21:13

问题


My Retrofit 2 (2.0.2 currently) client needs to add custom headers to requests.

I'm using an Interceptor to add these headers to all requests:

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

Some headers I always want to add, but some headers I only need to add based on requirements of that specific endpoint, for example whether the user needs to be authenticated or not.

I'd like to have the ability to control that at the api level, for example using an annotation, something like:

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

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

When sending a request to register there's no need to add the authentication token, but requests who lack the @NO_AUTH annotation will have the token header.

From what I understand Retrofit 2 doesn't support custom annotations, and while I found this workaround for Custom Annotations with Retrofit 2, it's seems a bit too much.

I'd like to avoid the need to pass these headers per request, like:

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

It just feels redundant to do it every time I call the method instead of doing it in the interceptor (since I have access to the header values statically).
I just somehow need to know in my Interceptor.intercept implementation whether or not this specific request should have a specific header(s).

Any idea how I can make this work?
I prefer a generic solution and not just for the auth token case, but a specific solution is welcome as well. Thanks


回答1:


I came up with a very simple and elegant (in my opinion) solution to my problem, and probably for other scenarios.

I use the Headers annotation to pass my custom annotations, and since OkHttp requires that they follow the Name: Value format, I decided that my format will be: @: ANNOTATION_NAME.

So basically:

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

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

Then I can intercept the request, check whether I have an annotation with name @. If so, I get the value and remove the header from the request.
This works well even if you want to have more than one "custom annotation":

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

Here's how to extract all of these "custom annotations" and remove them from the request:

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:


Maybe you can do that by creating different Retrofit object factory method like this.

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

if you want to call api without header auth, you can just call createService method:

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

And use createServiceWithAuth method if you want to call api with authentication:

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


来源:https://stackoverflow.com/questions/37757520/retrofit-2-elegant-way-of-adding-headers-in-the-api-level

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!