diff --git a/org.springframework.web/src/main/java/org/springframework/http/client/AbstractClientHttpRequestFactoryWrapper.java b/org.springframework.web/src/main/java/org/springframework/http/client/AbstractClientHttpRequestFactoryWrapper.java new file mode 100644 index 00000000000..80c689de45a --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/http/client/AbstractClientHttpRequestFactoryWrapper.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * 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 org.springframework.http.client; + +import java.io.IOException; +import java.net.URI; + +import org.springframework.http.HttpMethod; +import org.springframework.util.Assert; + +/** + * Abstract base class for {@link ClientHttpRequestFactory} implementations that decorate another request factory. + * + * @author Arjen Poutsma + * @since 3.1 + */ +public abstract class AbstractClientHttpRequestFactoryWrapper implements ClientHttpRequestFactory { + + 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) { + Assert.notNull(requestFactory, "'requestFactory' must not be null"); + this.requestFactory = requestFactory; + } + + /** + * {@inheritDoc} + * + *

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. + *

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; + +} diff --git a/org.springframework.web/src/main/java/org/springframework/http/client/BufferingClientHttpRequest.java b/org.springframework.web/src/main/java/org/springframework/http/client/BufferingClientHttpRequest.java new file mode 100644 index 00000000000..c5e4abf6b90 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/http/client/BufferingClientHttpRequest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * 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 org.springframework.http.client; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.util.Assert; +import org.springframework.util.FileCopyUtils; + +/** + * Simple implementation of {@link ClientHttpRequest} that wraps another request. + * + * @author Arjen Poutsma + * @since 3.1 + */ +class BufferingClientHttpRequest extends AbstractBufferingClientHttpRequest { + + private final ClientHttpRequest request; + + BufferingClientHttpRequest(ClientHttpRequest request) { + Assert.notNull(request, "'request' must not be null"); + this.request = request; + } + + public HttpMethod getMethod() { + return request.getMethod(); + } + + public URI getURI() { + return request.getURI(); + } + + @Override + protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException { + request.getHeaders().putAll(headers); + OutputStream body = request.getBody(); + FileCopyUtils.copy(bufferedOutput, body); + ClientHttpResponse response = request.execute(); + return new BufferingClientHttpResponse(response); + } +} diff --git a/org.springframework.web/src/main/java/org/springframework/http/client/BufferingClientHttpRequestFactory.java b/org.springframework.web/src/main/java/org/springframework/http/client/BufferingClientHttpRequestFactory.java new file mode 100644 index 00000000000..c48c0b572fc --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/http/client/BufferingClientHttpRequestFactory.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * 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 org.springframework.http.client; + +import java.io.IOException; +import java.net.URI; + +import org.springframework.http.HttpMethod; + +/** + * Wrapper for a {@link ClientHttpRequestFactory} that buffers all outgoing and incoming streams in memory. + * + *

Using this wrapper allows for multiple reads of the {@linkplain ClientHttpResponse#getBody() response body}. + * + * @author Arjen Poutsma + * @since 3.1 + */ +public class BufferingClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper { + + public BufferingClientHttpRequestFactory(ClientHttpRequestFactory requestFactory) { + super(requestFactory); + } + + @Override + protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) + throws IOException { + ClientHttpRequest request = requestFactory.createRequest(uri, httpMethod); + if (shouldBuffer(uri, httpMethod)) { + return new BufferingClientHttpRequest(request); + } + else { + return request; + } + } + + /** + * Indicates whether the request/response exchange for the given URI and method should be buffered in memory. + * + *

Default implementation returns {@code true} for all URIs and methods. Subclasses can override this method to + * change this behavior. + * + * @param uri the URI + * @param httpMethod the method + * @return {@code true} if the exchange should be buffered; {@code false} otherwise + */ + protected boolean shouldBuffer(URI uri, HttpMethod httpMethod) { + return true; + } +} diff --git a/org.springframework.web/src/main/java/org/springframework/http/client/BufferingClientHttpResponse.java b/org.springframework.web/src/main/java/org/springframework/http/client/BufferingClientHttpResponse.java new file mode 100644 index 00000000000..955b9707e07 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/http/client/BufferingClientHttpResponse.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * 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 org.springframework.http.client; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.springframework.http.HttpHeaders; +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()}. + * + * @author Arjen Poutsma + * @since 3.1 + */ +class BufferingClientHttpResponse implements ClientHttpResponse { + + private final ClientHttpResponse response; + + private byte[] body; + + BufferingClientHttpResponse(ClientHttpResponse response) { + this.response = response; + } + + public HttpStatus getStatusCode() throws IOException { + return response.getStatusCode(); + } + + public String getStatusText() throws IOException { + return response.getStatusText(); + } + + public HttpHeaders getHeaders() { + return response.getHeaders(); + } + + public InputStream getBody() throws IOException { + if (body == null) { + body = FileCopyUtils.copyToByteArray(response.getBody()); + } + return new ByteArrayInputStream(body); + } + + public void close() { + response.close(); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequestFactory.java b/org.springframework.web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequestFactory.java index b5b0fb91e28..66b692c879d 100644 --- a/org.springframework.web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequestFactory.java +++ b/org.springframework.web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequestFactory.java @@ -16,7 +16,6 @@ package org.springframework.http.client; -import java.io.IOException; import java.net.URI; import org.springframework.http.HttpMethod; @@ -28,9 +27,7 @@ import org.springframework.util.Assert; * @author Arjen Poutsma * @since 3.1 */ -public class InterceptingClientHttpRequestFactory implements ClientHttpRequestFactory { - - private final ClientHttpRequestFactory requestFactory; +public class InterceptingClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper { private final ClientHttpRequestInterceptor[] interceptors; @@ -42,12 +39,13 @@ public class InterceptingClientHttpRequestFactory implements ClientHttpRequestFa */ public InterceptingClientHttpRequestFactory(ClientHttpRequestFactory requestFactory, ClientHttpRequestInterceptor[] interceptors) { + super(requestFactory); Assert.notNull(requestFactory, "'requestFactory' must not be null"); - this.requestFactory = requestFactory; this.interceptors = interceptors != null ? interceptors : new ClientHttpRequestInterceptor[0]; } - public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { + @Override + protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) { return new InterceptingClientHttpRequest(requestFactory, interceptors, uri, httpMethod); } } diff --git a/org.springframework.web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTestCase.java b/org.springframework.web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTestCase.java index 14d7d43b950..9a610936318 100644 --- a/org.springframework.web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTestCase.java +++ b/org.springframework.web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTestCase.java @@ -107,12 +107,17 @@ public abstract class AbstractHttpRequestFactoryTestCase { request.getHeaders().setContentLength(body.length); FileCopyUtils.copy(body, request.getBody()); ClientHttpResponse response = request.execute(); - assertEquals("Invalid status code", HttpStatus.OK, response.getStatusCode()); - assertTrue("Header not found", response.getHeaders().containsKey(headerName)); - assertEquals("Header value not found", Arrays.asList(headerValue1, headerValue2), - response.getHeaders().get(headerName)); - byte[] result = FileCopyUtils.copyToByteArray(response.getBody()); - assertTrue("Invalid body", Arrays.equals(body, result)); + try { + assertEquals("Invalid status code", HttpStatus.OK, response.getStatusCode()); + assertTrue("Header not found", response.getHeaders().containsKey(headerName)); + assertEquals("Header value not found", Arrays.asList(headerValue1, headerValue2), + response.getHeaders().get(headerName)); + byte[] result = FileCopyUtils.copyToByteArray(response.getBody()); + assertTrue("Invalid body", Arrays.equals(body, result)); + } + finally { + response.close(); + } } @Test(expected = IllegalStateException.class) @@ -169,7 +174,9 @@ public abstract class AbstractHttpRequestFactoryTestCase { } } - /** Servlet that sets a given status code. */ + /** + * Servlet that sets a given status code. + */ private static class StatusServlet extends GenericServlet { private final int sc; diff --git a/org.springframework.web/src/test/java/org/springframework/http/client/BufferingClientHttpRequestFactoryTests.java b/org.springframework.web/src/test/java/org/springframework/http/client/BufferingClientHttpRequestFactoryTests.java new file mode 100644 index 00000000000..f862d0cb311 --- /dev/null +++ b/org.springframework.web/src/test/java/org/springframework/http/client/BufferingClientHttpRequestFactoryTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * 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 org.springframework.http.client; + +import java.net.URI; +import java.util.Arrays; + +import org.junit.Test; + +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.util.FileCopyUtils; + +import static org.junit.Assert.*; + +public class BufferingClientHttpRequestFactoryTests extends AbstractHttpRequestFactoryTestCase { + + @Override + protected ClientHttpRequestFactory createRequestFactory() { + return new BufferingClientHttpRequestFactory(new HttpComponentsClientHttpRequestFactory()); + } + + @Test + public void repeatableRead() throws Exception { + ClientHttpRequest request = factory.createRequest(new URI(baseUrl + "/echo"), HttpMethod.PUT); + assertEquals("Invalid HTTP method", HttpMethod.PUT, request.getMethod()); + String headerName = "MyHeader"; + String headerValue1 = "value1"; + request.getHeaders().add(headerName, headerValue1); + String headerValue2 = "value2"; + request.getHeaders().add(headerName, headerValue2); + byte[] body = "Hello World".getBytes("UTF-8"); + request.getHeaders().setContentLength(body.length); + FileCopyUtils.copy(body, request.getBody()); + ClientHttpResponse response = request.execute(); + try { + assertEquals("Invalid status code", HttpStatus.OK, response.getStatusCode()); + assertEquals("Invalid status code", HttpStatus.OK, response.getStatusCode()); + + assertTrue("Header not found", response.getHeaders().containsKey(headerName)); + assertTrue("Header not found", response.getHeaders().containsKey(headerName)); + + assertEquals("Header value not found", Arrays.asList(headerValue1, headerValue2), + response.getHeaders().get(headerName)); + assertEquals("Header value not found", Arrays.asList(headerValue1, headerValue2), + response.getHeaders().get(headerName)); + + byte[] result = FileCopyUtils.copyToByteArray(response.getBody()); + assertTrue("Invalid body", Arrays.equals(body, result)); + FileCopyUtils.copyToByteArray(response.getBody()); + assertTrue("Invalid body", Arrays.equals(body, result)); + } + finally { + response.close(); + } + } + +}