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 * @author Arjen Poutsma
* @since 3.0.6 * @since 3.0.6
*/ */
public abstract class AbstractBufferingClientHttpRequest extends AbstractClientHttpRequest { abstract class AbstractBufferingClientHttpRequest extends AbstractClientHttpRequest {
private ByteArrayOutputStream bufferedOutput = new ByteArrayOutputStream(); private ByteArrayOutputStream bufferedOutput = new ByteArrayOutputStream();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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