SPR-7909 - Sending large payloads with RestTemplate results in an OutOfMemoryError

This commit is contained in:
Arjen Poutsma
2011-01-25 16:08:15 +00:00
parent 33674933ea
commit d0d6a07870
12 changed files with 388 additions and 65 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -17,6 +17,7 @@
package org.springframework.http.client;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Arrays;
import java.util.Enumeration;
@@ -45,12 +46,12 @@ import static org.junit.Assert.*;
public abstract class AbstractHttpRequestFactoryTestCase {
private ClientHttpRequestFactory factory;
protected ClientHttpRequestFactory factory;
protected static String baseUrl;
private static Server jettyServer;
private static String baseUrl;
@BeforeClass
public static void startJettyServer() throws Exception {
int port = FreePortScanner.getFreePort();
@@ -64,7 +65,7 @@ public abstract class AbstractHttpRequestFactoryTestCase {
jettyContext.addServlet(new ServletHolder(new MethodServlet("GET")), "/methods/get");
jettyContext.addServlet(new ServletHolder(new MethodServlet("HEAD")), "/methods/head");
jettyContext.addServlet(new ServletHolder(new MethodServlet("OPTIONS")), "/methods/options");
jettyContext.addServlet(new ServletHolder(new MethodServlet("POST")), "/methods/post");
jettyContext.addServlet(new ServletHolder(new PostServlet()), "/methods/post");
jettyContext.addServlet(new ServletHolder(new MethodServlet("PUT")), "/methods/put");
jettyContext.addServlet(new ServletHolder(new RedirectServlet("/status/ok")), "/redirect");
jettyServer.start();
@@ -87,8 +88,7 @@ public abstract class AbstractHttpRequestFactoryTestCase {
@Test
public void status() throws Exception {
URI uri = new URI(baseUrl + "/status/notfound");
ClientHttpRequest request =
factory.createRequest(uri, HttpMethod.GET);
ClientHttpRequest request = factory.createRequest(uri, HttpMethod.GET);
assertEquals("Invalid HTTP method", HttpMethod.GET, request.getMethod());
assertEquals("Invalid HTTP URI", uri, request.getURI());
ClientHttpResponse response = request.execute();
@@ -105,6 +105,7 @@ public abstract class AbstractHttpRequestFactoryTestCase {
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();
assertEquals("Invalid status code", HttpStatus.OK, response.getStatusCode());
@@ -159,6 +160,7 @@ public abstract class AbstractHttpRequestFactoryTestCase {
try {
ClientHttpRequest request = factory.createRequest(new URI(baseUrl + "/methods/" + path), method);
response = request.execute();
assertEquals("Invalid response status", HttpStatus.OK, response.getStatusCode());
assertEquals("Invalid method", path.toUpperCase(Locale.ENGLISH), request.getMethod().name());
}
finally {
@@ -167,16 +169,18 @@ public abstract class AbstractHttpRequestFactoryTestCase {
}
}
}
@Test
public void redirect() throws Exception {
ClientHttpResponse response = null;
try {
ClientHttpRequest request = factory.createRequest(new URI(baseUrl + "/redirect"), HttpMethod.PUT);
response = request.execute();
assertEquals("Invalid Location value", new URI(baseUrl + "/status/ok"), response.getHeaders().getLocation());
assertEquals("Invalid Location value", new URI(baseUrl + "/status/ok"),
response.getHeaders().getLocation());
} finally {
}
finally {
if (response != null) {
response.close();
response = null;
@@ -187,13 +191,14 @@ public abstract class AbstractHttpRequestFactoryTestCase {
response = request.execute();
assertNull("Invalid Location value", response.getHeaders().getLocation());
} finally {
}
finally {
if (response != null) {
response.close();
}
}
}
/** Servlet that sets a given status code. */
private static class StatusServlet extends GenericServlet {
@@ -221,6 +226,31 @@ public abstract class AbstractHttpRequestFactoryTestCase {
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest httpReq = (HttpServletRequest) req;
assertEquals("Invalid HTTP method", method, httpReq.getMethod());
res.setContentLength(0);
((HttpServletResponse) res).setStatus(200);
}
}
private static class PostServlet extends MethodServlet {
private PostServlet() {
super("POST");
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
super.service(req, res);
long contentLength = req.getContentLength();
if (contentLength != -1) {
InputStream in = req.getInputStream();
long byteCount = 0;
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
byteCount += bytesRead;
}
assertEquals("Invalid content-length", contentLength, byteCount);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* 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.
@@ -16,7 +16,7 @@
package org.springframework.http.client;
public class SimpleHttpRequestFactoryTests extends AbstractHttpRequestFactoryTestCase {
public class BufferedSimpleHttpRequestFactoryTests extends AbstractHttpRequestFactoryTestCase {
@Override
protected ClientHttpRequestFactory createRequestFactory() {

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.OutputStream;
import java.net.URI;
import java.util.Random;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import static org.junit.Assert.*;
public class StreamingSimpleHttpRequestFactoryTests extends AbstractHttpRequestFactoryTestCase {
@Override
protected ClientHttpRequestFactory createRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setStreaming(true);
return factory;
}
@Test
@Ignore
public void largeFileUpload() throws Exception {
Random rnd = new Random();
ClientHttpResponse response = null;
try {
ClientHttpRequest request = factory.createRequest(new URI(baseUrl + "/methods/post"), HttpMethod.POST);
final int BUF_SIZE = 4096;
final int ITERATIONS = Integer.MAX_VALUE / BUF_SIZE;
final int contentLength = ITERATIONS * BUF_SIZE;
// request.getHeaders().setContentLength(contentLength);
OutputStream body = request.getBody();
for (int i=0; i < ITERATIONS; i++) {
byte[] buffer = new byte[BUF_SIZE];
rnd.nextBytes(buffer);
body.write(buffer);
}
response = request.execute();
assertEquals("Invalid response status", HttpStatus.OK, response.getStatusCode());
}
finally {
if (response != null) {
response.close();
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -30,7 +30,6 @@ import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.RequestContext;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
@@ -45,9 +44,9 @@ import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* @author Arjen Poutsma
*/
import static org.junit.Assert.*;
/** @author Arjen Poutsma */
public class FormHttpMessageConverterTests {
private FormHttpMessageConverter converter;
@@ -60,13 +59,13 @@ public class FormHttpMessageConverterTests {
@Test
public void canRead() {
assertTrue(converter.canRead(MultiValueMap.class, new MediaType("application", "x-www-form-urlencoded")));
assertFalse(converter.canRead(MultiValueMap.class, new MediaType("multipart","form-data")));
assertFalse(converter.canRead(MultiValueMap.class, new MediaType("multipart", "form-data")));
}
@Test
public void canWrite() {
assertTrue(converter.canWrite(MultiValueMap.class, new MediaType("application", "x-www-form-urlencoded")));
assertTrue(converter.canWrite(MultiValueMap.class, new MediaType("multipart","form-data")));
assertTrue(converter.canWrite(MultiValueMap.class, new MediaType("multipart", "form-data")));
assertTrue(converter.canWrite(MultiValueMap.class, MediaType.ALL));
}
@@ -77,7 +76,7 @@ public class FormHttpMessageConverterTests {
Charset iso88591 = Charset.forName("ISO-8859-1");
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(iso88591));
inputMessage.getHeaders().setContentType(new MediaType("application", "x-www-form-urlencoded", iso88591));
MultiValueMap<String, String> result = (MultiValueMap<String, String>) converter.read(null, inputMessage);
MultiValueMap<String, String> result = converter.read(null, inputMessage);
assertEquals("Invalid result", 3, result.size());
assertEquals("Invalid result", "value 1", result.getFirst("name 1"));
@@ -97,19 +96,21 @@ public class FormHttpMessageConverterTests {
body.add("name 3", null);
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
converter.write(body, MediaType.APPLICATION_FORM_URLENCODED, outputMessage);
Charset iso88591 = Charset.forName("ISO-8859-1");
assertEquals("Invalid result", "name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3",
outputMessage.getBodyAsString(iso88591));
outputMessage.getBodyAsString(Charset.forName("UTF-8")));
assertEquals("Invalid content-type", new MediaType("application", "x-www-form-urlencoded"),
outputMessage.getHeaders().getContentType());
assertEquals("Invalid content-length", outputMessage.getBodyAsBytes().length,
outputMessage.getHeaders().getContentLength());
}
@Test
public void writeMultipart() throws Exception {
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
parts.add("name 1", "value 1");
parts.add("name 2", "value 2+1");
parts.add("name 2", "value 2+2");
Resource logo = new ClassPathResource("/org/springframework/http/converter/logo.jpg");
parts.add("logo", logo);
Source xml = new StreamSource(new StringReader("<root><child/></root>"));
@@ -122,7 +123,7 @@ public class FormHttpMessageConverterTests {
converter.write(parts, MediaType.MULTIPART_FORM_DATA, outputMessage);
final MediaType contentType = outputMessage.getHeaders().getContentType();
assertNotNull(contentType.getParameter("boundary"));
assertNotNull("No boundary found", contentType.getParameter("boundary"));
// see if Commons FileUpload can read what we wrote
FileItemFactory fileItemFactory = new DiskFileItemFactory();
@@ -157,6 +158,7 @@ public class FormHttpMessageConverterTests {
}
private static class MockHttpOutputMessageRequestContext implements RequestContext {
private final MockHttpOutputMessage outputMessage;
private MockHttpOutputMessageRequestContext(MockHttpOutputMessage outputMessage) {