提交 d0d6a078 编写于 作者: A Arjen Poutsma

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

上级 33674933
/*
* 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.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import org.springframework.http.HttpHeaders;
/**
* Abstract base for {@link ClientHttpRequest} that buffers output in a byte array before sending it over the wire.
*
* @author Arjen Poutsma
* @since 3.0.6
*/
abstract class AbstractBufferingClientHttpRequest extends AbstractClientHttpRequest {
private ByteArrayOutputStream bufferedOutput = new ByteArrayOutputStream();
@Override
protected OutputStream getBodyInternal(HttpHeaders headers) throws IOException {
return this.bufferedOutput;
}
@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
byte[] bytes = this.bufferedOutput.toByteArray();
if (headers.getContentLength() == -1) {
headers.setContentLength(bytes.length);
}
ClientHttpResponse result = executeInternal(headers, bytes);
this.bufferedOutput = null;
return result;
}
/**
* Abstract template method that writes the given headers and content to the HTTP request.
* @param headers the HTTP headers
* @param bufferedOutput the body content
* @return the response object for the executed request
*/
protected abstract ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput)
throws IOException;
}
/*
* 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.
......@@ -16,7 +16,6 @@
package org.springframework.http.client;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
......@@ -29,27 +28,32 @@ import org.springframework.util.Assert;
* @author Arjen Poutsma
* @since 3.0
*/
public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
abstract class AbstractClientHttpRequest implements ClientHttpRequest {
private boolean executed = false;
private final HttpHeaders headers = new HttpHeaders();
private final ByteArrayOutputStream bufferedOutput = new ByteArrayOutputStream();
public final HttpHeaders getHeaders() {
return executed ? HttpHeaders.readOnlyHttpHeaders(headers) : this.headers;
}
public final OutputStream getBody() throws IOException {
checkExecuted();
return this.bufferedOutput;
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, this.bufferedOutput.toByteArray());
ClientHttpResponse result = executeInternal(this.headers);
this.executed = true;
return result;
}
......@@ -58,14 +62,13 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
Assert.state(!this.executed, "ClientHttpRequest already executed");
}
/**
* Abstract template method that writes the given headers and content to the HTTP request.
*
* @param headers the HTTP headers
* @param bufferedOutput the body content
* @return the response object for the executed request
*/
protected abstract ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput)
throws IOException;
protected abstract ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException;
}
/*
* 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.
......@@ -28,19 +28,18 @@ import org.springframework.http.HttpMethod;
import org.springframework.util.FileCopyUtils;
/**
* {@link ClientHttpRequest} implementation that uses standard J2SE facilities to execute requests.
* {@link ClientHttpRequest} implementation that uses standard J2SE facilities to execute buffered requests.
* Created via the {@link SimpleClientHttpRequestFactory}.
*
* @author Arjen Poutsma
* @since 3.0
* @see SimpleClientHttpRequestFactory#createRequest(java.net.URI, HttpMethod)
*/
final class SimpleClientHttpRequest extends AbstractClientHttpRequest {
final class BufferingSimpleClientHttpRequest extends AbstractBufferingClientHttpRequest {
private final HttpURLConnection connection;
SimpleClientHttpRequest(HttpURLConnection connection) {
BufferingSimpleClientHttpRequest(HttpURLConnection connection) {
this.connection = connection;
}
......
/*
* 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.
......@@ -41,7 +41,7 @@ import org.springframework.http.HttpMethod;
* @since 3.0
* @see CommonsClientHttpRequestFactory#createRequest(java.net.URI, HttpMethod)
*/
final class CommonsClientHttpRequest extends AbstractClientHttpRequest {
final class CommonsClientHttpRequest extends AbstractBufferingClientHttpRequest {
private final HttpClient httpClient;
......
/*
* 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.
......@@ -36,8 +36,14 @@ import org.springframework.util.Assert;
*/
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.
*/
......@@ -45,16 +51,48 @@ public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory
this.proxy = proxy;
}
/**
* 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).
*
* @see #setChunkSize(int)
* @see HttpURLConnection#setFixedLengthStreamingMode(int)
*/
public void setBufferRequestBody(boolean bufferRequestBody) {
this.bufferRequestBody = bufferRequestBody;
}
/**
* Sets the number of bytes to write in each chunk when not buffering request bodies locally.
* <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);
prepareConnection(connection, httpMethod.name());
return new SimpleClientHttpRequest(connection);
if (bufferRequestBody) {
return new BufferingSimpleClientHttpRequest(connection);
}
else {
return new StreamingSimpleClientHttpRequest(connection, 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}
......@@ -68,8 +106,8 @@ public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory
}
/**
* 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.)
......
/*
* 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.
......@@ -26,7 +26,8 @@ import org.springframework.util.StringUtils;
/**
* {@link ClientHttpResponse} implementation that uses standard J2SE facilities.
* Obtained via the {@link SimpleClientHttpRequest#execute()}.
* Obtained via {@link BufferingSimpleClientHttpRequest#execute()} and
* {@link StreamingSimpleClientHttpRequest#execute()}.
*
* @author Arjen Poutsma
* @since 3.0
......
/*
* 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.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
/**
* {@link ClientHttpRequest} implementation that uses standard J2SE facilities to execute streaming requests.
* Created via the {@link SimpleClientHttpRequestFactory}.
*
* @author Arjen Poutsma
* @since 3.0
* @see SimpleClientHttpRequestFactory#createRequest(java.net.URI, HttpMethod)
*/
public class StreamingSimpleClientHttpRequest extends AbstractClientHttpRequest {
private final HttpURLConnection connection;
private final int chunkSize;
private OutputStream body;
StreamingSimpleClientHttpRequest(HttpURLConnection connection, int chunkSize) {
this.connection = connection;
this.chunkSize = chunkSize;
}
public HttpMethod getMethod() {
return HttpMethod.valueOf(this.connection.getRequestMethod());
}
public URI getURI() {
try {
return this.connection.getURL().toURI();
}
catch (URISyntaxException ex) {
throw new IllegalStateException("Could not get HttpURLConnection URI: " + ex.getMessage(), ex);
}
}
@Override
protected OutputStream getBodyInternal(HttpHeaders headers) throws IOException {
if (body == null) {
int contentLength = (int) headers.getContentLength();
if (contentLength >= 0) {
this.connection.setFixedLengthStreamingMode(contentLength);
}
else {
this.connection.setChunkedStreamingMode(chunkSize);
}
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
String headerName = entry.getKey();
for (String headerValue : entry.getValue()) {
this.connection.addRequestProperty(headerName, headerValue);
}
}
this.connection.connect();
this.body = this.connection.getOutputStream();
}
return new NonClosingOutputStream(body);
}
@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
try {
if (body != null) {
body.close();
}
}
catch (IOException ex) {
// ignore
}
return new SimpleClientHttpResponse(this.connection);
}
private static class NonClosingOutputStream extends FilterOutputStream {
private NonClosingOutputStream(OutputStream out) {
super(out);
}
@Override
public void close() throws IOException {
}
}
}
/*
* 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.
......@@ -19,7 +19,6 @@ package org.springframework.http.converter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
......@@ -94,19 +93,25 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
this.partConverters.add(new ResourceHttpMessageConverter());
}
/** Add a message body converter. Such a converters is used to convert objects to MIME parts. */
/**
* Add a message body converter. Such a converters is used to convert objects to MIME parts.
*/
public final void addPartConverter(HttpMessageConverter<?> partConverter) {
Assert.notNull(partConverter, "'partConverter' must not be NULL");
this.partConverters.add(partConverter);
}
/** Set the message body converters to use. These converters are used to convert objects to MIME parts. */
/**
* Set the message body converters to use. These converters are used to convert objects to MIME parts.
*/
public final void setPartConverters(List<HttpMessageConverter<?>> partConverters) {
Assert.notEmpty(partConverters, "'partConverters' must not be empty");
this.partConverters = partConverters;
}
/** Sets the character set used for writing form data. */
/**
* Sets the character set used for writing form data.
*/
public void setCharset(Charset charset) {
this.charset = charset;
}
......@@ -196,7 +201,8 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
if (contentType != null) {
outputMessage.getHeaders().setContentType(contentType);
charset = contentType.getCharSet() != null ? contentType.getCharSet() : this.charset;
} else {
}
else {
outputMessage.getHeaders().setContentType(MediaType.APPLICATION_FORM_URLENCODED);
charset = this.charset;
}
......@@ -218,7 +224,9 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
builder.append('&');
}
}
FileCopyUtils.copy(builder.toString(), new OutputStreamWriter(outputMessage.getBody(), charset));
byte[] bytes = builder.toString().getBytes(charset.name());
outputMessage.getHeaders().setContentLength(bytes.length);
FileCopyUtils.copy(bytes, outputMessage.getBody());
}
private void writeMultipart(MultiValueMap<String, Object> parts, HttpOutputMessage outputMessage)
......@@ -328,7 +336,9 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
}
}
/** Implementation of {@link org.springframework.http.HttpOutputMessage} used for writing multipart data. */
/**
* Implementation of {@link org.springframework.http.HttpOutputMessage} used for writing multipart data.
*/
private class MultipartHttpOutputMessage implements HttpOutputMessage {
private final HttpHeaders headers = new HttpHeaders();
......
/*
* 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,11 +46,11 @@ import static org.junit.Assert.*;
public abstract class AbstractHttpRequestFactoryTestCase {
private ClientHttpRequestFactory factory;
protected ClientHttpRequestFactory factory;
private static Server jettyServer;
protected static String baseUrl;
private static String baseUrl;
private static Server jettyServer;
@BeforeClass
public static void startJettyServer() throws Exception {
......@@ -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 {
......@@ -174,9 +176,11 @@ public abstract class AbstractHttpRequestFactoryTestCase {
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,7 +191,8 @@ public abstract class AbstractHttpRequestFactoryTestCase {
response = request.execute();
assertNull("Invalid Location value", response.getHeaders().getLocation());
} finally {
}
finally {
if (response != null) {
response.close();
}
......@@ -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);
}
}
}
......
/*
* 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() {
......
/*
* 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();
}
}
}
}
/*
* 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,11 +96,12 @@ 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
......@@ -110,6 +110,7 @@ public class FormHttpMessageConverterTests {
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) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册