web-dev-qa-db-ja.com

レトロフィット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はカスタムアノテーションをサポートしていないと私が理解していることから、 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この特定のリクエストに特定のヘッダーを含めるかどうかの実装。

この作業をどのように行うことができますか?
認証トークンの場合だけでなく、一般的なソリューションを好みますが、特定のソリューションも歓迎します。ありがとう

15
Nitzan Tomer

私の問題に対する、そしておそらく他のシナリオに対する、非常にシンプルでエレガントな(私の意見では)ソリューションを思いつきました。

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

たぶん、あなたはこのような異なるレトロフィットオブジェクトファクトリメソッドを作成することでそれを行うことができます。

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

また、認証付きでAPIを呼び出す場合は、createServiceWithAuthメソッドを使用します。

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