revised package-level request and response classes

This commit is contained in:
Juergen Hoeller 2011-07-13 23:14:02 +00:00
parent f5fdedea60
commit 0c2a6395e7
11 changed files with 83 additions and 81 deletions

View File

@ -28,7 +28,7 @@ import org.springframework.http.HttpHeaders;
* @author Arjen Poutsma
* @since 3.0.6
*/
public abstract class AbstractBufferingClientHttpRequest extends AbstractClientHttpRequest {
abstract class AbstractBufferingClientHttpRequest extends AbstractClientHttpRequest {
private ByteArrayOutputStream bufferedOutput = new ByteArrayOutputStream();

View File

@ -30,12 +30,13 @@ import org.springframework.util.Assert;
*/
public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
private boolean executed = false;
private final HttpHeaders headers = new HttpHeaders();
private boolean executed = false;
public final HttpHeaders getHeaders() {
return executed ? HttpHeaders.readOnlyHttpHeaders(headers) : this.headers;
return (this.executed ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
}
public final OutputStream getBody() throws IOException {
@ -43,14 +44,6 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
return getBodyInternal(this.headers);
}
/**
* Abstract template method that returns the body.
*
* @param headers the HTTP headers
* @return the body output stream
*/
protected abstract OutputStream getBodyInternal(HttpHeaders headers) throws IOException;
public final ClientHttpResponse execute() throws IOException {
checkExecuted();
ClientHttpResponse result = executeInternal(this.headers);
@ -62,13 +55,19 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
Assert.state(!this.executed, "ClientHttpRequest already executed");
}
/**
* Abstract template method that returns the body.
* @param headers the HTTP headers
* @return the body output stream
*/
protected abstract OutputStream getBodyInternal(HttpHeaders headers) throws IOException;
/**
* Abstract template method that writes the given headers and content to the HTTP request.
*
* @param headers the HTTP headers
* @return the response object for the executed request
*/
protected abstract ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException;
}

View File

@ -32,9 +32,9 @@ public abstract class AbstractClientHttpRequestFactoryWrapper implements ClientH
private final ClientHttpRequestFactory requestFactory;
/**
* Creates a {@code AbstractClientHttpRequestFactoryWrapper} wrapping the given request factory.
*
* @param requestFactory the request factory to be wrapped
*/
protected AbstractClientHttpRequestFactoryWrapper(ClientHttpRequestFactory requestFactory) {
@ -42,29 +42,27 @@ public abstract class AbstractClientHttpRequestFactoryWrapper implements ClientH
this.requestFactory = requestFactory;
}
/**
* {@inheritDoc}
*
* <p>This implementation simply calls {@link #createRequest(URI, HttpMethod, ClientHttpRequestFactory)} with the
* wrapped request factory provided to the {@linkplain #AbstractClientHttpRequestFactoryWrapper(ClientHttpRequestFactory)
* constructor}.
* This implementation simply calls {@link #createRequest(URI, HttpMethod, ClientHttpRequestFactory)}
* with the wrapped request factory provided to the
* {@linkplain #AbstractClientHttpRequestFactoryWrapper(ClientHttpRequestFactory) constructor}.
*/
public final ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
return createRequest(uri, httpMethod, requestFactory);
}
/**
* Create a new {@link ClientHttpRequest} for the specified URI and HTTP method by using the passed on request factory.
* Create a new {@link ClientHttpRequest} for the specified URI and HTTP method by using the
* passed-on request factory.
* <p>Called from {@link #createRequest(URI, HttpMethod)}.
*
* @param uri the URI to create a request for
* @param httpMethod the HTTP method to execute
* @param requestFactory the wrapped request factory
* @return the created request
* @throws IOException in case of I/O errors
*/
protected abstract ClientHttpRequest createRequest(URI uri,
HttpMethod httpMethod,
ClientHttpRequestFactory requestFactory) throws IOException;
protected abstract ClientHttpRequest createRequest(
URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) throws IOException;
}

View File

@ -40,7 +40,7 @@ public class BufferingClientHttpRequestFactory extends AbstractClientHttpRequest
throws IOException {
ClientHttpRequest request = requestFactory.createRequest(uri, httpMethod);
if (shouldBuffer(uri, httpMethod)) {
return new BufferingClientHttpRequest(request);
return new BufferingClientHttpRequestWrapper(request);
}
else {
return request;

View File

@ -31,29 +31,32 @@ import org.springframework.util.FileCopyUtils;
* @author Arjen Poutsma
* @since 3.1
*/
class BufferingClientHttpRequest extends AbstractBufferingClientHttpRequest {
final class BufferingClientHttpRequestWrapper extends AbstractBufferingClientHttpRequest {
private final ClientHttpRequest request;
BufferingClientHttpRequest(ClientHttpRequest request) {
BufferingClientHttpRequestWrapper(ClientHttpRequest request) {
Assert.notNull(request, "'request' must not be null");
this.request = request;
}
public HttpMethod getMethod() {
return request.getMethod();
return this.request.getMethod();
}
public URI getURI() {
return request.getURI();
return this.request.getURI();
}
@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
request.getHeaders().putAll(headers);
OutputStream body = request.getBody();
this.request.getHeaders().putAll(headers);
OutputStream body = this.request.getBody();
FileCopyUtils.copy(bufferedOutput, body);
ClientHttpResponse response = request.execute();
return new BufferingClientHttpResponse(response);
ClientHttpResponse response = this.request.execute();
return new BufferingClientHttpResponseWrapper(response);
}
}

View File

@ -25,43 +25,45 @@ import org.springframework.http.HttpStatus;
import org.springframework.util.FileCopyUtils;
/**
* Simple implementation of {@link ClientHttpResponse} that reads the request's body into memory, thus allowing for
* multiple invocations of {@link #getBody()}.
* Simple implementation of {@link ClientHttpResponse} that reads the request's body into memory,
* thus allowing for multiple invocations of {@link #getBody()}.
*
* @author Arjen Poutsma
* @since 3.1
*/
class BufferingClientHttpResponse implements ClientHttpResponse {
final class BufferingClientHttpResponseWrapper implements ClientHttpResponse {
private final ClientHttpResponse response;
private byte[] body;
BufferingClientHttpResponse(ClientHttpResponse response) {
BufferingClientHttpResponseWrapper(ClientHttpResponse response) {
this.response = response;
}
public HttpStatus getStatusCode() throws IOException {
return response.getStatusCode();
return this.response.getStatusCode();
}
public String getStatusText() throws IOException {
return response.getStatusText();
return this.response.getStatusText();
}
public HttpHeaders getHeaders() {
return response.getHeaders();
return this.response.getHeaders();
}
public InputStream getBody() throws IOException {
if (body == null) {
body = FileCopyUtils.copyToByteArray(response.getBody());
if (this.body == null) {
this.body = FileCopyUtils.copyToByteArray(this.response.getBody());
}
return new ByteArrayInputStream(body);
return new ByteArrayInputStream(this.body);
}
public void close() {
response.close();
this.response.close();
}
}

View File

@ -60,7 +60,7 @@ public class CommonsClientHttpRequestFactory implements ClientHttpRequestFactory
* {@link HttpClient} that uses a default {@link MultiThreadedHttpConnectionManager}.
*/
public CommonsClientHttpRequestFactory() {
httpClient = new HttpClient(new MultiThreadedHttpConnectionManager());
this.httpClient = new HttpClient(new MultiThreadedHttpConnectionManager());
this.setReadTimeout(DEFAULT_READ_TIMEOUT_MILLISECONDS);
}

View File

@ -35,11 +35,12 @@ import org.springframework.util.FileCopyUtils;
* @since 3.0
* @see SimpleClientHttpRequestFactory#createRequest(java.net.URI, HttpMethod)
*/
final class BufferingSimpleClientHttpRequest extends AbstractBufferingClientHttpRequest {
final class SimpleBufferingClientHttpRequest extends AbstractBufferingClientHttpRequest {
private final HttpURLConnection connection;
BufferingSimpleClientHttpRequest(HttpURLConnection connection) {
SimpleBufferingClientHttpRequest(HttpURLConnection connection) {
this.connection = connection;
}
@ -69,9 +70,7 @@ final class BufferingSimpleClientHttpRequest extends AbstractBufferingClientHttp
if (this.connection.getDoOutput()) {
this.connection.setFixedLengthStreamingMode(bufferedOutput.length);
}
this.connection.connect();
if (this.connection.getDoOutput()) {
FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());
}

View File

@ -30,22 +30,24 @@ import org.springframework.util.Assert;
* {@link ClientHttpRequestFactory} implementation that uses standard J2SE facilities.
*
* @author Arjen Poutsma
* @since 3.0
* @see java.net.HttpURLConnection
* @see CommonsClientHttpRequestFactory
* @since 3.0
*/
public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory {
private static final int DEFAULT_CHUNK_SIZE = 4096;
private Proxy proxy;
private boolean bufferRequestBody = true;
private int chunkSize = DEFAULT_CHUNK_SIZE;
/**
* Sets the {@link Proxy} to use for this request factory.
* Set the {@link Proxy} to use for this request factory.
*/
public void setProxy(Proxy proxy) {
this.proxy = proxy;
@ -54,12 +56,11 @@ public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory
/**
* Indicates whether this request factory should buffer the {@linkplain ClientHttpRequest#getBody() request body}
* internally.
* <p>Default is {@code true}. When sending large amounts of data via POST or PUT, it is recommended to change this
* property to {@code false}, so as not to run out of memory. This will result in a {@link ClientHttpRequest}
* that either streams directly to the underlying {@link HttpURLConnection} (if the
* {@link org.springframework.http.HttpHeaders#getContentLength() Content-Length} is known in advance), or that will
* use "Chunked transfer encoding" (if the {@code Content-Length} is not known in advance).
*
* <p>Default is {@code true}. When sending large amounts of data via POST or PUT, it is recommended
* to change this property to {@code false}, so as not to run out of memory. This will result in a
* {@link ClientHttpRequest} that either streams directly to the underlying {@link HttpURLConnection}
* (if the {@link org.springframework.http.HttpHeaders#getContentLength() Content-Length} is known in advance),
* or that will use "Chunked transfer encoding" (if the {@code Content-Length} is not known in advance).
* @see #setChunkSize(int)
* @see HttpURLConnection#setFixedLengthStreamingMode(int)
*/
@ -72,43 +73,42 @@ public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory
* <p>Note that this parameter is only used when {@link #setBufferRequestBody(boolean) bufferRequestBody} is set
* to {@code false}, and the {@link org.springframework.http.HttpHeaders#getContentLength() Content-Length}
* is not known in advance.
*
* @see #setBufferRequestBody(boolean)
*/
public void setChunkSize(int chunkSize) {
this.chunkSize = chunkSize;
}
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
HttpURLConnection connection = openConnection(uri.toURL(), proxy);
HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
prepareConnection(connection, httpMethod.name());
if (bufferRequestBody) {
return new BufferingSimpleClientHttpRequest(connection);
if (this.bufferRequestBody) {
return new SimpleBufferingClientHttpRequest(connection);
}
else {
return new StreamingSimpleClientHttpRequest(connection, chunkSize);
return new SimpleStreamingClientHttpRequest(connection, this.chunkSize);
}
}
/**
* Opens and returns a connection to the given URL. <p>The default implementation uses the given {@linkplain
* #setProxy(java.net.Proxy) proxy} - if any - to open a connection.
*
* Opens and returns a connection to the given URL.
* <p>The default implementation uses the given {@linkplain #setProxy(java.net.Proxy) proxy} -
* if any - to open a connection.
* @param url the URL to open a connection to
* @param proxy the proxy to use, may be {@code null}
* @return the opened connection
* @throws IOException in case of I/O errors
*/
protected HttpURLConnection openConnection(URL url, Proxy proxy) throws IOException {
URLConnection urlConnection = proxy != null ? url.openConnection(proxy) : url.openConnection();
URLConnection urlConnection = (proxy != null ? url.openConnection(proxy) : url.openConnection());
Assert.isInstanceOf(HttpURLConnection.class, urlConnection);
return (HttpURLConnection) urlConnection;
}
/**
* Template method for preparing the given {@link HttpURLConnection}. <p>The default implementation prepares the
* connection for input and output, and sets the HTTP method.
*
* Template method for preparing the given {@link HttpURLConnection}.
* <p>The default implementation prepares the connection for input and output, and sets the HTTP method.
* @param connection the connection to prepare
* @param httpMethod the HTTP request method ({@code GET}, {@code POST}, etc.)
* @throws IOException in case of I/O errors
@ -130,4 +130,4 @@ public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory
connection.setRequestMethod(httpMethod);
}
}
}

View File

@ -26,8 +26,8 @@ import org.springframework.util.StringUtils;
/**
* {@link ClientHttpResponse} implementation that uses standard J2SE facilities.
* Obtained via {@link BufferingSimpleClientHttpRequest#execute()} and
* {@link StreamingSimpleClientHttpRequest#execute()}.
* Obtained via {@link SimpleBufferingClientHttpRequest#execute()} and
* {@link SimpleStreamingClientHttpRequest#execute()}.
*
* @author Arjen Poutsma
* @since 3.0

View File

@ -36,7 +36,7 @@ import org.springframework.http.HttpMethod;
* @since 3.0
* @see SimpleClientHttpRequestFactory#createRequest(java.net.URI, HttpMethod)
*/
public class StreamingSimpleClientHttpRequest extends AbstractClientHttpRequest {
final class SimpleStreamingClientHttpRequest extends AbstractClientHttpRequest {
private final HttpURLConnection connection;
@ -44,7 +44,8 @@ public class StreamingSimpleClientHttpRequest extends AbstractClientHttpRequest
private OutputStream body;
StreamingSimpleClientHttpRequest(HttpURLConnection connection, int chunkSize) {
SimpleStreamingClientHttpRequest(HttpURLConnection connection, int chunkSize) {
this.connection = connection;
this.chunkSize = chunkSize;
}
@ -64,13 +65,13 @@ public class StreamingSimpleClientHttpRequest extends AbstractClientHttpRequest
@Override
protected OutputStream getBodyInternal(HttpHeaders headers) throws IOException {
if (body == null) {
if (this.body == null) {
int contentLength = (int) headers.getContentLength();
if (contentLength >= 0) {
this.connection.setFixedLengthStreamingMode(contentLength);
}
else {
this.connection.setChunkedStreamingMode(chunkSize);
this.connection.setChunkedStreamingMode(this.chunkSize);
}
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
String headerName = entry.getKey();
@ -81,14 +82,14 @@ public class StreamingSimpleClientHttpRequest extends AbstractClientHttpRequest
this.connection.connect();
this.body = this.connection.getOutputStream();
}
return new NonClosingOutputStream(body);
return new NonClosingOutputStream(this.body);
}
@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
try {
if (body != null) {
body.close();
if (this.body != null) {
this.body.close();
}
}
catch (IOException ex) {
@ -97,6 +98,7 @@ public class StreamingSimpleClientHttpRequest extends AbstractClientHttpRequest
return new SimpleClientHttpResponse(this.connection);
}
private static class NonClosingOutputStream extends FilterOutputStream {
private NonClosingOutputStream(OutputStream out) {
@ -108,5 +110,4 @@ public class StreamingSimpleClientHttpRequest extends AbstractClientHttpRequest
}
}
}