Matt Sheppard
Matt Sheppard

Reputation: 118023

Recording OkHttp response body size without buffering the whole thing

I want to be able to record the actual size of web server response bodies as I fetch things using OkHttp (for monitoring purposes).

Is there a way I can get the actual size of the response body (note - not the content-length the server claimed) without the cost of buffering the entire response into memory?

Upvotes: 3

Views: 4479

Answers (1)

Matt Sheppard
Matt Sheppard

Reputation: 118023

This seems to work for what I wanted...

AtomicLong bytesRead = new AtomicLong();

OkHttpClient client = new OkHttpClient.Builder().addNetworkInterceptor(new Interceptor() {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Response response = chain.proceed(chain.request());

        response = response.newBuilder()
            .body(
                new RealResponseBody(
                    response.body().contentType().toString(),
                    response.body().contentLength(),
                    Okio.buffer(
                        new LengthTrackingSource(
                            response.body().source(),
                            (newBytesRead) -> {
                                bytesRead.addAndGet(newBytesRead);
                            }
                        )
                    )
                )
            )
            .build();

        return response;
    }

    class LengthTrackingSource implements Source {

        private Source source; 


        private Consumer<Long> lengthRecorder;

        public LengthTrackingSource(Source source, Consumer<Long> lengthRecorder) {
            this.source = source;
            this.lengthRecorder = lengthRecorder;
        }

        @Override
        public long read(Buffer sink, long byteCount) throws IOException {
            long bytesRead;
            try {
                bytesRead = source.read(sink, byteCount);
            } catch (IOException e) {
                throw e;
            }

            // Avoid adding the final -1 (which means the source is exhausted)
            if (bytesRead > 0) {
                lengthRecorder.accept(bytesRead);
            }

            return bytesRead;
        }

        @Override
        public Timeout timeout() {
            return source.timeout();
        }

        @Override
        public void close() throws IOException {
            source.close();
        }
    }

}).build();

try (Response response = client.newCall(new Request.Builder().url("http://example.com/").build()).execute()) {
    System.out.println(bytesRead.get());

    String body = response.body().string();
    System.out.println(body.length());

    System.out.println(bytesRead.get());
}

If there's a simpler way I'd still love to hear about it!

Upvotes: 1

Related Questions