Roberto Fernandez
Roberto Fernandez

Reputation: 129

OkHttp API rate limit

Has OkHttp an integrated way of complying with an api rate request limit, or it has to be implemented externally? either case a hint on where to start is appreciated.

Upvotes: 8

Views: 7385

Answers (4)

Italo Borssatto
Italo Borssatto

Reputation: 15689

An interceptor combined with a RateLimiter from Guava was a good solution to avoid receiving a 429 HTTP code.

Let's suppose we want a limit of 3 calls per second:

import java.io.IOException;

import com.google.common.util.concurrent.RateLimiter;

import okhttp3.Interceptor;
import okhttp3.Response;

public class RateLimitInterceptor implements Interceptor {
    private RateLimiter rateLimiter = RateLimiter.create(3);

    @Override
    public Response intercept(Chain chain) throws IOException {
        rateLimiter.acquire(1);
        return chain.proceed(chain.request());
    }
}

Upvotes: 9

user8251233
user8251233

Reputation: 19

I also have the problem. I want limit rate when upload large file by post. I read OkHttp Interceptors code. And find can limite body write to limit upload rate.

public class RateLimitingRequestBody extends RequestBody {

private MediaType mContentType;
private File mFile;
private int mMaxRate;    // ms/bit

private RateLimitingRequestBody(@Nullable final MediaType contentType, final File file, int rate){
    mContentType = contentType;
    mFile = file;
    mMaxRate = rate;
}

@Override
public MediaType contentType() {
    return mContentType;
}

@Override
public void writeTo(BufferedSink sink) throws IOException {

    Source source = null;

    try {
        source = Okio.source(mFile);
        writeAll(sink, source);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        Util.closeQuietly(source);
    }
}


public long writeAll(BufferedSink sink, Source source) throws IOException, InterruptedException {
    if (source == null) {
        throw new IllegalArgumentException("source == null");
    } else {
        long totalBytesRead = 0L;

        long readCount;
        long start = System.currentTimeMillis();
        while((readCount = source.read(sink.buffer(), 8192L)) != -1L) {
            totalBytesRead += readCount;
            sink.emitCompleteSegments();

            long time = System.currentTimeMillis();
            if(time == start) continue;
            long rate = (totalBytesRead * 8) / (time - start);
            NLog.v("writeAll","totalBytesRead:"+totalBytesRead+"B "+ " Rate:"+rate*1000+"bits");

            if(rate > mMaxRate/1000){
                int sleep = (int) (totalBytesRead * 8 * 1000 / mMaxRate - (time - start));
                NLog.d("writeAll", "sleep:"+sleep);
                Thread.sleep(sleep+50);
            }
        }

        long end = System.currentTimeMillis();
        long rate = (totalBytesRead * 8 * 1000) / ((end - start));
        NLog.e("writeAll","totalBytesRead:"+totalBytesRead+"B "+ " Rate:"+rate+"bits"+" total time:"+(end-start));
        return totalBytesRead;
    }
}


public static RequestBody createRequestBody(@Nullable final MediaType contentType, final File file, int rate) {
    if (file == null) {
        throw new NullPointerException("content == null");
    } else {
        return new RateLimitingRequestBody(contentType, file, rate);
    }
}

}

May be this can help you.

Upvotes: 1

user3188040
user3188040

Reputation: 711

As @jesse-wilson said, you can do this with OkHttp Interceptors

Here's an example. First define a custom Interceptor. The api I call responds with HTTP Code 429 when the rate limit is hit. You will need to check for the particular HTTP Code or Header in your own api that indicates a rate error, and sleep for an appropriate time.

public class RateLimitInterceptor implements Interceptor {

    public RateLimitInterceptor() {
    }

    @Override
    public Response intercept(Chain chain) throws IOException {

        Response response = chain.proceed(chain.request());

        // 429 is how the api indicates a rate limit error
        if (!response.isSuccessful() && response.code() == 429) {
            System.err.println("Cloudant: "+response.message());

            // wait & retry
            try {
                System.out.println("wait and retry...");
                Thread.sleep(1000);
            } catch (InterruptedException e) {}

            response = chain.proceed(chain.request());
        }

        return response;
    }
}

Next add the Interceptor to where you build the OkHttp request. Here's an example of my builder...

public static Response fetchPaged(HttpUrl url) throws IOException {
        OkHttpClient client = new OkHttpClient.Builder()
                .addInterceptor(new BasicAuthInterceptor(username, password))
                .addInterceptor(new RateLimitInterceptor())
                .build();

        Request request = new Request.Builder()
                .url(url)
                .build();

        return client
                .newCall(request)
                .execute();
    }

Upvotes: 8

Jesse Wilson
Jesse Wilson

Reputation: 40593

You can build an interceptor to track requests made, and potentially throttle or fail requests if the rate is too high.

Upvotes: 1

Related Questions