/*
*
* Copyright 2017 Christopher Pilsworth
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
*/
package io.github.resilience4j.retrofit;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RequestNotPermitted;
import io.github.resilience4j.retrofit.internal.DecoratedCall;
import io.vavr.CheckedFunction0;
import io.vavr.control.Try;
import okhttp3.MediaType;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Response;
import java.io.IOException;
/**
* Decorates a Retrofit {@link Call} to check with a {@link RateLimiter} if a call can be made.
* Returns an error response with a HTTP 429 (too many requests) code and a message which indicates that the client
* prevented the request.
*
* <p>
* <code>
* RetrofitRateLimiter.decorateCall(rateLimiter, call);
* </code>
*/
public interface RetrofitRateLimiter {
int ERROR_CODE = 429;
String ERROR_MESSAGE = "Too many requests for the client";
/**
* Decorate {@link Call}s allow {@link CircuitBreaker} functionality.
*
* @param rateLimiter {@link RateLimiter} to apply
* @param call Call to decorate
* @param <T> Response type of call
* @return Original Call decorated with CircuitBreaker
*/
static <T> Call<T> decorateCall(final RateLimiter rateLimiter, final Call<T> call) {
return new DecoratedCall<T>(call) {
@Override
public Response<T> execute() throws IOException {
CheckedFunction0<Response<T>> restrictedSupplier = RateLimiter.decorateCheckedSupplier(rateLimiter, call::execute);
final Try<Response<T>> response = Try.of(restrictedSupplier);
return response.isSuccess() ? response.get() : handleFailure(response);
}
private Response<T> handleFailure(Try<Response<T>> response) throws IOException {
try {
throw response.getCause();
} catch (RequestNotPermitted | IllegalStateException e) {
return tooManyRequestsError();
} catch (IOException ioe) {
throw ioe;
} catch (Throwable t) {
throw new RuntimeException("Exception executing call", t);
}
}
private Response<T> tooManyRequestsError() {
return Response.error(ERROR_CODE, ResponseBody.create(MediaType.parse("text/plain"), ERROR_MESSAGE));
}
};
}
}