OutOfMemory Errors when posting large objects via RestTemplate

This commit is contained in:
Arjen Poutsma 2011-01-25 09:48:19 +00:00
parent 2ed3d77331
commit 33674933ea
4 changed files with 115 additions and 59 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.
@ -18,15 +18,12 @@ package org.springframework.http.converter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import javax.activation.FileTypeMap;
import javax.activation.MimetypesFileTypeMap;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
@ -44,49 +41,28 @@ import org.springframework.util.StringUtils;
* @author Arjen Poutsma
* @since 3.0.2
*/
public class ResourceHttpMessageConverter implements HttpMessageConverter<Resource> {
public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<Resource> {
private static final boolean jafPresent =
ClassUtils.isPresent("javax.activation.FileTypeMap", ResourceHttpMessageConverter.class.getClassLoader());
public boolean canRead(Class<?> clazz, MediaType mediaType) {
public ResourceHttpMessageConverter() {
super(MediaType.ALL);
}
@Override
protected boolean supports(Class<?> clazz) {
return Resource.class.isAssignableFrom(clazz);
}
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return Resource.class.isAssignableFrom(clazz);
}
public List<MediaType> getSupportedMediaTypes() {
return Collections.singletonList(MediaType.ALL);
}
public Resource read(Class<? extends Resource> clazz, HttpInputMessage inputMessage)
@Override
protected Resource readInternal(Class<? extends Resource> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
byte[] body = FileCopyUtils.copyToByteArray(inputMessage.getBody());
return new ByteArrayResource(body);
}
public void write(Resource resource, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
HttpHeaders headers = outputMessage.getHeaders();
if (contentType == null || contentType.isWildcardType() || contentType.isWildcardSubtype()) {
contentType = getContentType(resource);
}
if (contentType != null) {
headers.setContentType(contentType);
}
Long contentLength = getContentLength(resource, contentType);
if (contentLength != null) {
headers.setContentLength(contentLength);
}
FileCopyUtils.copy(resource.getInputStream(), outputMessage.getBody());
outputMessage.getBody().flush();
}
private MediaType getContentType(Resource resource) {
@Override
protected MediaType getDefaultContentType(Resource resource) {
if (jafPresent) {
return ActivationMediaTypeFactory.getMediaType(resource);
}
@ -95,10 +71,22 @@ public class ResourceHttpMessageConverter implements HttpMessageConverter<Resour
}
}
protected Long getContentLength(Resource resource, MediaType contentType) throws IOException {
return resource.contentLength();
@Override
protected Long getContentLength(Resource resource, MediaType contentType) {
try {
return resource.contentLength();
}
catch (IOException e) {
return null;
}
}
@Override
protected void writeInternal(Resource resource, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
FileCopyUtils.copy(resource.getInputStream(), outputMessage.getBody());
outputMessage.getBody().flush();
}
/**
* Inner class to avoid hard-coded JAF dependency.

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.
@ -67,25 +67,19 @@ public class StringHttpMessageConverter extends AbstractHttpMessageConverter<Str
@Override
protected String readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException {
MediaType contentType = inputMessage.getHeaders().getContentType();
Charset charset = contentType.getCharSet() != null ? contentType.getCharSet() : DEFAULT_CHARSET;
Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());
return FileCopyUtils.copyToString(new InputStreamReader(inputMessage.getBody(), charset));
}
@Override
protected Long getContentLength(String s, MediaType contentType) {
if (contentType != null && contentType.getCharSet() != null) {
Charset charset = contentType.getCharSet();
try {
return (long) s.getBytes(charset.name()).length;
}
catch (UnsupportedEncodingException ex) {
// should not occur
throw new InternalError(ex.getMessage());
}
Charset charset = getContentTypeCharset(contentType);
try {
return (long) s.getBytes(charset.name()).length;
}
else {
return null;
catch (UnsupportedEncodingException ex) {
// should not occur
throw new InternalError(ex.getMessage());
}
}
@ -94,8 +88,7 @@ public class StringHttpMessageConverter extends AbstractHttpMessageConverter<Str
if (writeAcceptCharset) {
outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());
}
MediaType contentType = outputMessage.getHeaders().getContentType();
Charset charset = contentType.getCharSet() != null ? contentType.getCharSet() : DEFAULT_CHARSET;
Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());
FileCopyUtils.copy(s, new OutputStreamWriter(outputMessage.getBody(), charset));
}
@ -110,4 +103,13 @@ public class StringHttpMessageConverter extends AbstractHttpMessageConverter<Str
return this.availableCharsets;
}
private Charset getContentTypeCharset(MediaType contentType) {
if (contentType != null && contentType.getCharSet() != null) {
return contentType.getCharSet();
}
else {
return DEFAULT_CHARSET;
}
}
}

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.
@ -19,6 +19,7 @@ package org.springframework.http.converter.xml;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
@ -31,13 +32,14 @@ import javax.xml.transform.stream.StreamSource;
import org.xml.sax.InputSource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
/**
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter}
* that can read and write {@link Source} objects.
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter} that can read and write {@link
* Source} objects.
*
* @author Arjen Poutsma
* @since 3.0
@ -84,6 +86,21 @@ public class SourceHttpMessageConverter<T extends Source> extends AbstractXmlHtt
return new ByteArrayInputStream(bos.toByteArray());
}
@Override
protected Long getContentLength(T t, MediaType contentType) {
if (t instanceof DOMSource) {
try {
CountingOutputStream os = new CountingOutputStream();
transform(t, new StreamResult(os));
return os.count;
}
catch (TransformerException ex) {
// ignore
}
}
return null;
}
@Override
protected void writeToResult(T t, HttpHeaders headers, Result result) throws IOException {
try {
@ -94,4 +111,24 @@ public class SourceHttpMessageConverter<T extends Source> extends AbstractXmlHtt
}
}
private static class CountingOutputStream extends OutputStream {
private long count = 0;
@Override
public void write(int b) throws IOException {
count++;
}
@Override
public void write(byte[] b) throws IOException {
count += b.length;
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
count += len;
}
}
}

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.converter.xml;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.nio.charset.Charset;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Source;
@ -24,7 +25,6 @@ import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import static org.custommonkey.xmlunit.XMLAssert.*;
import org.junit.Before;
import org.junit.Test;
import org.w3c.dom.Document;
@ -36,6 +36,8 @@ import org.springframework.http.MockHttpInputMessage;
import org.springframework.http.MockHttpOutputMessage;
import org.springframework.util.FileCopyUtils;
import static org.custommonkey.xmlunit.XMLAssert.*;
/** @author Arjen Poutsma */
@SuppressWarnings("unchecked")
public class SourceHttpMessageConverterTests {
@ -100,7 +102,7 @@ public class SourceHttpMessageConverterTests {
}
@Test
public void write() throws Exception {
public void writeDOMSource() throws Exception {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
Document document = documentBuilderFactory.newDocumentBuilder().newDocument();
@ -115,7 +117,34 @@ public class SourceHttpMessageConverterTests {
outputMessage.getBodyAsString(Charset.forName("UTF-8")));
assertEquals("Invalid content-type", new MediaType("application", "xml"),
outputMessage.getHeaders().getContentType());
assertEquals("Invalid content-length", outputMessage.getBodyAsBytes().length,
outputMessage.getHeaders().getContentLength());
}
@Test
public void writeSAXSource() throws Exception {
String xml = "<root>Hello World</root>";
SAXSource saxSource = new SAXSource(new InputSource(new StringReader(xml)));
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
converter.write(saxSource, null, outputMessage);
assertXMLEqual("Invalid result", "<root>Hello World</root>",
outputMessage.getBodyAsString(Charset.forName("UTF-8")));
assertEquals("Invalid content-type", new MediaType("application", "xml"),
outputMessage.getHeaders().getContentType());
}
@Test
public void writeStreamSource() throws Exception {
String xml = "<root>Hello World</root>";
StreamSource streamSource = new StreamSource(new StringReader(xml));
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
converter.write(streamSource, null, outputMessage);
assertXMLEqual("Invalid result", "<root>Hello World</root>",
outputMessage.getBodyAsString(Charset.forName("UTF-8")));
assertEquals("Invalid content-type", new MediaType("application", "xml"),
outputMessage.getHeaders().getContentType());
}
}