Buffering RequestFactory that allows for multiple response body reads.

This commit is contained in:
Arjen Poutsma 2011-04-21 15:09:44 +00:00
parent 945cf43e2d
commit a4716c2a94
7 changed files with 349 additions and 13 deletions

View File

@ -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}
*
* <p>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.
* <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;
}

View File

@ -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);
}
}

View File

@ -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.
*
* <p>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.
*
* <p>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;
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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();
}
}
}