OkHttp API 速率限制

8
OkHttp是否具有内置的符合API请求限制的方法,还是必须在外部实现?无论哪种情况,都需要提示从哪里开始。
4个回答

8

一个与Guava的RateLimiter结合的拦截器是避免收到429 HTTP状态码的好解决方案。

假设我们想要每秒调用3次:

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

1
请注意,RateLimiter被标记为不稳定(@Beta)。https://github.com/google/guava/blob/master/guava/src/com/google/common/util/concurrent/RateLimiter.java#L95 - Burst

7

如@jesse-wilson所说,您可以使用OkHttp拦截器来实现这个功能。

下面是一个示例。首先定义一个自定义的拦截器。我调用的API在达到速率限制时会响应HTTP Code 429。您需要检查您自己的API中指示速率错误的特定HTTP代码或标头,并休眠适当的时间。

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

接下来,在构建OkHttp请求的位置添加拦截器。这是我的构建器示例...

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

1
你可以构建一个拦截器来跟踪请求,如果速率过高,可能会限制或失败请求。

这个问题是关于限流部分,我不知道如何实现,理想情况下是告诉客户端延迟队列,因为请求可能同时来自多个片段,但我找不到方法来做到这一点。 - Roberto Fernandez
Guava中有一个速率限制器。您可以使用它,或者研究其源代码以开始使用。还可以考虑使用OkHttp的异步API来限制活动线程的数量。Dispatcher具有配置选项。 - Jesse Wilson
谢谢你的建议,我会去了解一下。 - Roberto Fernandez

1
我也有这个问题。我想通过POST上传大文件时限制速率。我阅读了OkHttp拦截器代码,并发现可以限制写入的主体以限制上传速率。
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);
    }
}

}

也许这可以帮到你。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接