com.facebook.presto.jdbc.internal.okhttp3.ResponseBody Maven / Gradle / Ivy
/*
* Copyright (C) 2014 Square, Inc.
*
* 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 com.facebook.presto.jdbc.internal.okhttp3;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import com.facebook.presto.jdbc.internal.javax.annotation.Nullable;
import com.facebook.presto.jdbc.internal.okhttp3.internal.Util;
import com.facebook.presto.jdbc.internal.okio.Buffer;
import com.facebook.presto.jdbc.internal.okio.BufferedSource;
import static com.facebook.presto.jdbc.internal.okhttp3.internal.Util.UTF_8;
/**
* A one-shot stream from the origin server to the client application with the raw bytes of the
* response body. Each response body is supported by an active connection to the webserver. This
* imposes both obligations and limits on the client application.
*
* The response body must be closed.
*
* Each response body is backed by a limited resource like a socket (live network responses) or
* an open file (for cached responses). Failing to close the response body will leak resources and
* may ultimately cause the application to slow down or crash.
*
* Both this class and {@link Response} implement {@link Closeable}. Closing a response simply
* closes its response body. If you invoke {@link Call#execute()} or implement {@link
* Callback#onResponse} you must close this body by calling any of the following methods:
*
*
* - Response.close()
* - Response.body().close()
* - Response.body().source().close()
* - Response.body().charStream().close()
* - Response.body().byteString().close()
* - Response.body().bytes()
* - Response.body().string()
*
*
* There is no benefit to invoking multiple {@code close()} methods for the same response body.
*
*
For synchronous calls, the easiest way to make sure a response body is closed is with a {@code
* try} block. With this structure the compiler inserts an implicit {@code finally} clause that
* calls {@code close()} for you.
*
*
{@code
*
* Call call = client.newCall(request);
* try (Response response = call.execute()) {
* ... // Use the response.
* }
* }
*
* You can use a similar block for asynchronous calls: {@code
*
* Call call = client.newCall(request);
* call.enqueue(new Callback() {
* public void onResponse(Call call, Response response) throws IOException {
* try (ResponseBody responseBody = response.body()) {
* ... // Use the response.
* }
* }
*
* public void onFailure(Call call, IOException e) {
* ... // Handle the failure.
* }
* });
* }
*
* These examples will not work if you're consuming the response body on another thread. In such
* cases the consuming thread must call {@link #close} when it has finished reading the response
* body.
*
* The response body can be consumed only once.
*
* This class may be used to stream very large responses. For example, it is possible to use this
* class to read a response that is larger than the entire memory allocated to the current process.
* It can even stream a response larger than the total storage on the current device, which is a
* common requirement for video streaming applications.
*
*
Because this class does not buffer the full response in memory, the application may not
* re-read the bytes of the response. Use this one shot to read the entire response into memory with
* {@link #bytes()} or {@link #string()}. Or stream the response with either {@link #source()},
* {@link #byteStream()}, or {@link #charStream()}.
*/
public abstract class ResponseBody implements Closeable {
/** Multiple calls to {@link #charStream()} must return the same instance. */
private Reader reader;
public abstract @Nullable MediaType contentType();
/**
* Returns the number of bytes in that will returned by {@link #bytes}, or {@link #byteStream}, or
* -1 if unknown.
*/
public abstract long contentLength();
public final InputStream byteStream() {
return source().inputStream();
}
public abstract BufferedSource source();
/**
* Returns the response as a byte array.
*
*
This method loads entire response body into memory. If the response body is very large this
* may trigger an {@link OutOfMemoryError}. Prefer to stream the response body if this is a
* possibility for your response.
*/
public final byte[] bytes() throws IOException {
long contentLength = contentLength();
if (contentLength > Integer.MAX_VALUE) {
throw new IOException("Cannot buffer entire body for content length: " + contentLength);
}
BufferedSource source = source();
byte[] bytes;
try {
bytes = source.readByteArray();
} finally {
Util.closeQuietly(source);
}
if (contentLength != -1 && contentLength != bytes.length) {
throw new IOException("Content-Length ("
+ contentLength
+ ") and stream length ("
+ bytes.length
+ ") disagree");
}
return bytes;
}
/**
* Returns the response as a character stream decoded with the charset of the Content-Type header.
* If that header is either absent or lacks a charset, this will attempt to decode the response
* body in accordance to its BOM or
* UTF-8.
*/
public final Reader charStream() {
Reader r = reader;
return r != null ? r : (reader = new BomAwareReader(source(), charset()));
}
/**
* Returns the response as a string decoded with the charset of the Content-Type header. If that
* header is either absent or lacks a charset, this will attempt to decode the response body in
* accordance to its BOM or UTF-8.
* Closes {@link ResponseBody} automatically.
*
*
This method loads entire response body into memory. If the response body is very large this
* may trigger an {@link OutOfMemoryError}. Prefer to stream the response body if this is a
* possibility for your response.
*/
public final String string() throws IOException {
BufferedSource source = source();
try {
Charset charset = Util.bomAwareCharset(source, charset());
return source.readString(charset);
} finally {
Util.closeQuietly(source);
}
}
private Charset charset() {
MediaType contentType = contentType();
return contentType != null ? contentType.charset(UTF_8) : UTF_8;
}
@Override public void close() {
Util.closeQuietly(source());
}
/**
* Returns a new response body that transmits {@code content}. If {@code contentType} is non-null
* and lacks a charset, this will use UTF-8.
*/
public static ResponseBody create(@Nullable MediaType contentType, String content) {
Charset charset = UTF_8;
if (contentType != null) {
charset = contentType.charset();
if (charset == null) {
charset = UTF_8;
contentType = MediaType.parse(contentType + "; charset=utf-8");
}
}
Buffer buffer = new Buffer().writeString(content, charset);
return create(contentType, buffer.size(), buffer);
}
/** Returns a new response body that transmits {@code content}. */
public static ResponseBody create(final @Nullable MediaType contentType, byte[] content) {
Buffer buffer = new Buffer().write(content);
return create(contentType, content.length, buffer);
}
/** Returns a new response body that transmits {@code content}. */
public static ResponseBody create(final @Nullable MediaType contentType,
final long contentLength, final BufferedSource content) {
if (content == null) throw new NullPointerException("source == null");
return new ResponseBody() {
@Override public @Nullable MediaType contentType() {
return contentType;
}
@Override public long contentLength() {
return contentLength;
}
@Override public BufferedSource source() {
return content;
}
};
}
static final class BomAwareReader extends Reader {
private final BufferedSource source;
private final Charset charset;
private boolean closed;
private Reader delegate;
BomAwareReader(BufferedSource source, Charset charset) {
this.source = source;
this.charset = charset;
}
@Override public int read(char[] cbuf, int off, int len) throws IOException {
if (closed) throw new IOException("Stream closed");
Reader delegate = this.delegate;
if (delegate == null) {
Charset charset = Util.bomAwareCharset(source, this.charset);
delegate = this.delegate = new InputStreamReader(source.inputStream(), charset);
}
return delegate.read(cbuf, off, len);
}
@Override public void close() throws IOException {
closed = true;
if (delegate != null) {
delegate.close();
} else {
source.close();
}
}
}
}