com.amazonaws.http.HttpMethodReleaseInputStream Maven / Gradle / Ivy
Show all versions of aws-java-sdk-core Show documentation
/*
* Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Portions copyright 2006-2009 James Murty. Please see LICENSE.txt
* for applicable license terms and NOTICE.txt for applicable notices.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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.amazonaws.http;
import com.amazonaws.HttpMethod;
import com.amazonaws.internal.SdkInputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.client.methods.AbortableHttpRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Utility class to wrap InputStreams obtained from an HttpClient library's
* HttpMethod object, and ensure the stream and HTTP connection are properly
* released.
*
* This input stream wrapper is used to ensure that input streams obtained
* through HttpClient connections are cleaned up correctly once the caller has
* read all the contents of the connection's input stream, or closed the input
* stream. Additionally, this class attempts to release the connection and close
* the stream in this object's finalizer method, as a last resort to avoid
* leaking resources.
*
* Important! This input stream must be completely consumed or closed to
* ensure the necessary cleanup operations can be performed.
*/
public class HttpMethodReleaseInputStream extends SdkInputStream {
private static final Log log = LogFactory.getLog(HttpMethodReleaseInputStream.class);
private InputStream in;
private HttpEntityEnclosingRequest httpRequest;
private boolean alreadyReleased;
private boolean underlyingStreamConsumed;
/**
* Constructs an input stream based on an {@link HttpMethod} object
* representing an HTTP connection. If a connection input stream is
* available, this constructor wraps the underlying input stream and makes
* that stream available. If no underlying connection is available, an empty
* {@link ByteArrayInputStream} is made available.
*
* @param httpMethod The HTTP method being executed, whose response content
* is to be wrapped.
*/
@SuppressWarnings("checkstyle:emptyblock")
public HttpMethodReleaseInputStream(HttpEntityEnclosingRequest httpMethod) {
this.httpRequest = httpMethod;
try {
this.in = httpMethod.getEntity().getContent();
} catch (IOException e) {
if (log.isWarnEnabled()) {
log.warn("Unable to obtain HttpMethod's response data stream", e);
}
try {
httpMethod.getEntity().getContent().close();
} catch (Exception ex) {
}
this.in = new ByteArrayInputStream(new byte[0]); // Empty input
// stream;
}
}
/**
* Returns the underlying HttpMethod object that contains/manages the actual
* HTTP connection.
*
* @return the HTTPMethod object that provides the data input stream.
*/
public HttpEntityEnclosingRequest getHttpRequest() {
return httpRequest;
}
/**
* Forces the release of an HttpMethod's connection in a way that will
* perform all the necessary cleanup through the correct use of HttpClient
* methods.
*
* @throws IOException
*/
protected void releaseConnection() throws IOException {
if (!alreadyReleased) {
if (!underlyingStreamConsumed) {
// Underlying input stream has not been consumed, abort method
// to force connection to be closed and cleaned-up.
if (httpRequest instanceof AbortableHttpRequest) {
AbortableHttpRequest abortableHttpRequest = (AbortableHttpRequest) httpRequest;
abortableHttpRequest.abort();
}
}
in.close();
alreadyReleased = true;
}
}
/**
* Standard input stream read method, except it calls
* {@link #releaseConnection} when the underlying input stream is consumed.
*
* @see java.io.InputStream#read()
*/
@Override
public int read() throws IOException {
try {
int read = in.read();
if (read == -1) {
underlyingStreamConsumed = true;
if (!alreadyReleased) {
releaseConnection();
if (log.isDebugEnabled()) {
log.debug("Released HttpMethod as its response data stream is fully consumed");
}
}
}
return read;
} catch (IOException e) {
releaseConnection();
if (log.isDebugEnabled()) {
log.debug("Released HttpMethod as its response data stream threw an exception", e);
}
throw e;
}
}
/**
* Standard input stream read method, except it calls
* {@link #releaseConnection} when the underlying input stream is consumed.
*
* @see java.io.InputStream#read(byte[], int, int)
*/
@Override
public int read(byte[] b, int off, int len) throws IOException {
try {
int read = in.read(b, off, len);
if (read == -1) {
underlyingStreamConsumed = true;
if (!alreadyReleased) {
releaseConnection();
if (log.isDebugEnabled()) {
log.debug("Released HttpMethod as its response data stream is fully consumed");
}
}
}
return read;
} catch (IOException e) {
releaseConnection();
if (log.isDebugEnabled()) {
log.debug("Released HttpMethod as its response data stream threw an exception", e);
}
throw e;
}
}
/**
* Standard input stream available method, except it ensures that
* {@link #releaseConnection()} is called if any errors are encountered from
* the wrapped stream.
*
* @see java.io.InputStream#available()
*/
@Override
public int available() throws IOException {
try {
return in.available();
} catch (IOException e) {
releaseConnection();
if (log.isDebugEnabled()) {
log.debug("Released HttpMethod as its response data stream threw an exception", e);
}
throw e;
}
}
/**
* Standard input stream close method, except it ensures that
* {@link #releaseConnection()} is called before the input stream is closed.
*
* @see java.io.InputStream#close()
*/
@Override
public void close() throws IOException {
if (!alreadyReleased) {
releaseConnection();
if (log.isDebugEnabled()) {
log.debug("Released HttpMethod as its response data stream is closed");
}
}
in.close();
}
/**
* Tries to ensure a connection is always cleaned-up correctly by calling
* {@link #releaseConnection()} on class destruction if the cleanup hasn't
* already been done.
*
* This desperate cleanup act will only be necessary if the user of this
* class does not completely consume or close this input stream prior to
* object destruction. This method will log Warning messages if a forced
* cleanup is required, hopefully reminding the user to close their streams
* properly.
*/
@Override
protected void finalize() throws Throwable {
if (!alreadyReleased) {
if (log.isWarnEnabled()) {
log.warn("Attempting to release HttpMethod in finalize() as its response data stream has gone out of scope. "
+ "This attempt will not always succeed and cannot be relied upon! Please ensure S3 response data streams are "
+ "always fully consumed or closed to avoid HTTP connection starvation.");
}
releaseConnection();
if (log.isWarnEnabled()) {
log.warn("Successfully released HttpMethod in finalize(). You were lucky this time... "
+ "Please ensure S3 response data streams are always fully consumed or closed.");
}
}
super.finalize();
}
@Override
protected InputStream getWrappedInputStream() {
return in;
}
}