first bunch of backports from 3.1 M2 to 3.0.6

This commit is contained in:
Juergen Hoeller
2011-06-08 22:49:41 +00:00
parent 175f6d4bc5
commit ca19b14f13
31 changed files with 498 additions and 300 deletions

View File

@@ -1,11 +1,11 @@
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -177,11 +177,15 @@ public class HttpHeaders implements MultiValueMap<String, String> {
String[] tokens = value.split(",\\s*");
for (String token : tokens) {
int paramIdx = token.indexOf(';');
String charsetName;
if (paramIdx == -1) {
result.add(Charset.forName(token));
charsetName = token;
}
else {
result.add(Charset.forName(token.substring(0, paramIdx)));
charsetName = token.substring(0, paramIdx);
}
if (!charsetName.equals("*")) {
result.add(Charset.forName(charsetName));
}
}
}
@@ -310,7 +314,11 @@ public class HttpHeaders implements MultiValueMap<String, String> {
* @param eTag the new entity tag
*/
public void setETag(String eTag) {
set(ETAG, quote(eTag));
if (eTag != null) {
Assert.isTrue(eTag.startsWith("\"") || eTag.startsWith("W/"), "Invalid eTag, does not start with W/ or \"");
Assert.isTrue(eTag.endsWith("\""), "Invalid eTag, does not end with \"");
}
set(ETAG, eTag);
}
/**
@@ -318,7 +326,7 @@ public class HttpHeaders implements MultiValueMap<String, String> {
* @return the entity tag
*/
public String getETag() {
return unquote(getFirst(ETAG));
return getFirst(ETAG);
}
/**
@@ -362,7 +370,7 @@ public class HttpHeaders implements MultiValueMap<String, String> {
* @param ifNoneMatch the new value of the header
*/
public void setIfNoneMatch(String ifNoneMatch) {
set(IF_NONE_MATCH, quote(ifNoneMatch));
set(IF_NONE_MATCH, ifNoneMatch);
}
/**
@@ -373,7 +381,7 @@ public class HttpHeaders implements MultiValueMap<String, String> {
StringBuilder builder = new StringBuilder();
for (Iterator<String> iterator = ifNoneMatchList.iterator(); iterator.hasNext();) {
String ifNoneMatch = iterator.next();
builder.append(quote(ifNoneMatch));
builder.append(ifNoneMatch);
if (iterator.hasNext()) {
builder.append(", ");
}
@@ -392,7 +400,7 @@ public class HttpHeaders implements MultiValueMap<String, String> {
if (value != null) {
String[] tokens = value.split(",\\s*");
for (String token : tokens) {
result.add(unquote(token));
result.add(token);
}
}
return result;
@@ -452,31 +460,6 @@ public class HttpHeaders implements MultiValueMap<String, String> {
// Utility methods
private String quote(String s) {
Assert.notNull(s);
if (!s.startsWith("\"")) {
s = "\"" + s;
}
if (!s.endsWith("\"")) {
s = s + "\"";
}
return s;
}
private String unquote(String s) {
if (s == null) {
return null;
}
if (s.startsWith("\"")) {
s = s.substring(1);
}
if (s.endsWith("\"")) {
s = s.substring(0, s.length() - 1);
}
return s;
}
private long getFirstDate(String headerName) {
String headerValue = getFirst(headerName);
if (headerValue == null) {

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.
@@ -187,7 +187,7 @@ public class MediaType implements Comparable<MediaType> {
/**
* Create a new {@link MediaType} for the given primary type.
* Create a new {@code MediaType} for the given primary type.
* <p>The {@linkplain #getSubtype() subtype} is set to <code>&#42;</code>, parameters empty.
* @param type the primary type
* @throws IllegalArgumentException if any of the parameters contain illegal characters
@@ -197,7 +197,7 @@ public class MediaType implements Comparable<MediaType> {
}
/**
* Create a new {@link MediaType} for the given primary type and subtype.
* Create a new {@code MediaType} for the given primary type and subtype.
* <p>The parameters are empty.
* @param type the primary type
* @param subtype the subtype
@@ -208,18 +208,18 @@ public class MediaType implements Comparable<MediaType> {
}
/**
* Create a new {@link MediaType} for the given type, subtype, and character set.
* Create a new {@code MediaType} for the given type, subtype, and character set.
* @param type the primary type
* @param subtype the subtype
* @param charSet the character set
* @throws IllegalArgumentException if any of the parameters contain illegal characters
*/
public MediaType(String type, String subtype, Charset charSet) {
this(type, subtype, Collections.singletonMap(PARAM_CHARSET, charSet.toString()));
this(type, subtype, Collections.singletonMap(PARAM_CHARSET, charSet.name()));
}
/**
* Create a new {@link MediaType} for the given type, subtype, and quality value.
* Create a new {@code MediaType} for the given type, subtype, and quality value.
*
* @param type the primary type
* @param subtype the subtype
@@ -231,7 +231,7 @@ public class MediaType implements Comparable<MediaType> {
}
/**
* Copy-constructor that copies the type and subtype of the given {@link MediaType},
* Copy-constructor that copies the type and subtype of the given {@code MediaType},
* and allows for different parameter.
* @param other the other media type
* @param parameters the parameters, may be <code>null</code>
@@ -242,7 +242,7 @@ public class MediaType implements Comparable<MediaType> {
}
/**
* Create a new {@link MediaType} for the given type, subtype, and parameters.
* Create a new {@code MediaType} for the given type, subtype, and parameters.
* @param type the primary type
* @param subtype the subtype
* @param parameters the parameters, may be <code>null</code>
@@ -322,7 +322,7 @@ public class MediaType implements Comparable<MediaType> {
}
/**
* Indicate whether the {@linkplain #getType() type} is the wildcard character <code>&#42;</code> or not.
* Indicates whether the {@linkplain #getType() type} is the wildcard character <code>&#42;</code> or not.
*/
public boolean isWildcardType() {
return WILDCARD_TYPE.equals(type);
@@ -336,13 +336,22 @@ public class MediaType implements Comparable<MediaType> {
}
/**
* Indicate whether the {@linkplain #getSubtype() subtype} is the wildcard character <code>&#42;</code> or not.
* Indicates whether the {@linkplain #getSubtype() subtype} is the wildcard character <code>&#42;</code> or not.
* @return whether the subtype is <code>&#42;</code>
*/
public boolean isWildcardSubtype() {
return WILDCARD_TYPE.equals(subtype);
}
/**
* Indicates whether this media type is concrete, i.e. whether neither the type or subtype is a wildcard
* character <code>&#42;</code>.
* @return whether this media type is concrete
*/
public boolean isConcrete() {
return !isWildcardType() && !isWildcardSubtype();
}
/**
* Return the character set, as indicated by a <code>charset</code> parameter, if any.
* @return the character set; or <code>null</code> if not available
@@ -372,9 +381,9 @@ public class MediaType implements Comparable<MediaType> {
}
/**
* Indicate whether this {@link MediaType} includes the given media type.
* <p>For instance, {@code text/*} includes {@code text/plain}, {@code text/html}, and {@code application/*+xml}
* includes {@code application/soap+xml}, etc. This method is non-symmetic.
* Indicate whether this {@code MediaType} includes the given media type.
* <p>For instance, {@code text/*} includes {@code text/plain} and {@code text/html}, and {@code application/*+xml}
* includes {@code application/soap+xml}, etc. This method is <b>not</b> symmetric.
* @param other the reference media type with which to compare
* @return <code>true</code> if this media type includes the given media type; <code>false</code> otherwise
*/
@@ -407,9 +416,9 @@ public class MediaType implements Comparable<MediaType> {
}
/**
* Indicate whether this {@link MediaType} is compatible with the given media type.
* Indicate whether this {@code MediaType} is compatible with the given media type.
* <p>For instance, {@code text/*} is compatible with {@code text/plain}, {@code text/html}, and vice versa.
* In effect, this method is similar to {@link #includes(MediaType)}, except that it's symmetric.
* In effect, this method is similar to {@link #includes(MediaType)}, except that it <b>is</b> symmetric.
* @param other the reference media type with which to compare
* @return <code>true</code> if this media type is compatible with the given media type; <code>false</code> otherwise
*/
@@ -444,7 +453,7 @@ public class MediaType implements Comparable<MediaType> {
}
/**
* Compares this {@link MediaType} to another alphabetically.
* Compares this {@code MediaType} to another alphabetically.
* @param other media type to compare to
* @see #sortBySpecificity(List)
*/
@@ -533,7 +542,7 @@ public class MediaType implements Comparable<MediaType> {
/**
* Parse the given String value into a {@link MediaType} object,
* Parse the given String value into a {@code MediaType} object,
* with this method name following the 'valueOf' naming convention
* (as supported by {@link org.springframework.core.convert.ConversionService}.
* @see #parseMediaType(String)
@@ -543,7 +552,7 @@ public class MediaType implements Comparable<MediaType> {
}
/**
* Parse the given String into a single {@link MediaType}.
* Parse the given String into a single {@code MediaType}.
* @param mediaType the string to parse
* @return the media type
* @throws IllegalArgumentException if the string cannot be parsed
@@ -586,7 +595,7 @@ public class MediaType implements Comparable<MediaType> {
/**
* Parse the given, comma-seperated string into a list of {@link MediaType} objects.
* Parse the given, comma-separated string into a list of {@code MediaType} objects.
* <p>This method can be used to parse an Accept or Content-Type header.
* @param mediaTypes the string to parse
* @return the list of media types
@@ -605,7 +614,7 @@ public class MediaType implements Comparable<MediaType> {
}
/**
* Return a string representation of the given list of {@link MediaType} objects.
* Return a string representation of the given list of {@code MediaType} objects.
* <p>This method can be used to for an {@code Accept} or {@code Content-Type} header.
* @param mediaTypes the string to parse
* @return the list of media types
@@ -624,7 +633,7 @@ public class MediaType implements Comparable<MediaType> {
}
/**
* Sorts the given list of {@link MediaType} objects by specificity.
* Sorts the given list of {@code MediaType} objects by specificity.
* <p>Given two media types:
* <ol>
* <li>if either media type has a {@linkplain #isWildcardType() wildcard type}, then the media type without the
@@ -657,7 +666,7 @@ public class MediaType implements Comparable<MediaType> {
}
/**
* Sorts the given list of {@link MediaType} objects by quality value.
* Sorts the given list of {@code MediaType} objects by quality value.
* <p>Given two media types:
* <ol>
* <li>if the two media types have different {@linkplain #getQualityValue() quality value}, then the media type
@@ -684,7 +693,10 @@ public class MediaType implements Comparable<MediaType> {
}
static final Comparator<MediaType> SPECIFICITY_COMPARATOR = new Comparator<MediaType>() {
/**
* Comparator used by {@link #sortBySpecificity(List)}.
*/
public static final Comparator<MediaType> SPECIFICITY_COMPARATOR = new Comparator<MediaType>() {
public int compare(MediaType mediaType1, MediaType mediaType2) {
if (mediaType1.isWildcardType() && !mediaType2.isWildcardType()) { // */* < audio/*
@@ -724,7 +736,10 @@ public class MediaType implements Comparable<MediaType> {
};
static final Comparator<MediaType> QUALITY_VALUE_COMPARATOR = new Comparator<MediaType>() {
/**
* Comparator used by {@link #sortByQualityValue(List)}.
*/
public static final Comparator<MediaType> QUALITY_VALUE_COMPARATOR = new Comparator<MediaType>() {
public int compare(MediaType mediaType1, MediaType mediaType2) {
double quality1 = mediaType1.getQualityValue();

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,13 +19,11 @@ 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;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@@ -43,38 +41,28 @@ import org.springframework.util.FileCopyUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.util.WebUtils;
/**
* Implementation of {@link HttpMessageConverter} that can handle form data, including multipart form data
* (i.e. file uploads).
* Implementation of {@link HttpMessageConverter} that can handle form data, including multipart form data (i.e. file
* uploads).
*
* <p>This converter can write the {@code application/x-www-form-urlencoded} and {@code multipart/form-data} media
* types, and read the {@code application/x-www-form-urlencoded}) media type (but not {@code multipart/form-data}).
*
* <p>In other words, this converter can read and write 'normal' HTML forms (as
* {@link MultiValueMap MultiValueMap&lt;String, String&gt;}), and it can write multipart form (as
* {@link MultiValueMap MultiValueMap&lt;String, Object&gt;}. When writing multipart, this converter uses other
* {@link HttpMessageConverter HttpMessageConverters} to write the respective MIME parts. By default, basic converters
* are registered (supporting {@code Strings} and {@code Resources}, for instance); these can be overridden by setting
* the {@link #setPartConverters(java.util.List) partConverters} property.
* <p>In other words, this converter can read and write 'normal' HTML forms (as {@link MultiValueMap
* MultiValueMap&lt;String, String&gt;}), and it can write multipart form (as {@link MultiValueMap
* MultiValueMap&lt;String, Object&gt;}. When writing multipart, this converter uses other {@link HttpMessageConverter
* HttpMessageConverters} to write the respective MIME parts. By default, basic converters are registered (supporting
* {@code Strings} and {@code Resources}, for instance); these can be overridden by setting the {@link
* #setPartConverters(java.util.List) partConverters} property.
*
* <p>For example, the following snippet shows how to submit an HTML form:
* <pre class="code">
* RestTemplate template = new RestTemplate(); // FormHttpMessageConverter is configured by default
* MultiValueMap&lt;String, String&gt; form = new LinkedMultiValueMap&lt;String, String&gt;();
* form.add("field 1", "value 1");
* form.add("field 2", "value 2");
* form.add("field 2", "value 3");
* template.postForLocation("http://example.com/myForm", form);
* </pre>
* <p>The following snippet shows how to do a file upload:
* <pre class="code">
* MultiValueMap&lt;String, Object&gt; parts = new LinkedMultiValueMap&lt;String, Object&gt;();
* parts.add("field 1", "value 1");
* parts.add("file", new ClassPathResource("myFile.jpg"));
* template.postForLocation("http://example.com/myFileUpload", parts);
* </pre>
* <p>For example, the following snippet shows how to submit an HTML form: <pre class="code"> RestTemplate template =
* new RestTemplate(); // FormHttpMessageConverter is configured by default MultiValueMap&lt;String, String&gt; form =
* new LinkedMultiValueMap&lt;String, String&gt;(); form.add("field 1", "value 1"); form.add("field 2", "value 2");
* form.add("field 2", "value 3"); template.postForLocation("http://example.com/myForm", form); </pre> <p>The following
* snippet shows how to do a file upload: <pre class="code"> MultiValueMap&lt;String, Object&gt; parts = new
* LinkedMultiValueMap&lt;String, Object&gt;(); parts.add("field 1", "value 1"); parts.add("file", new
* ClassPathResource("myFile.jpg")); template.postForLocation("http://example.com/myFileUpload", parts); </pre>
*
* <p>Some methods in this class were inspired by {@link org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity}.
*
@@ -88,16 +76,21 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
new byte[]{'-', '_', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A',
'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
'V', 'W', 'X', 'Y', 'Z'};
'V', 'W', 'X', 'Y', 'Z'};
private final Random rnd = new Random();
private Charset charset = Charset.forName(WebUtils.DEFAULT_CHARACTER_ENCODING);
private Charset charset = Charset.forName("UTF-8");
private List<MediaType> supportedMediaTypes = new ArrayList<MediaType>();
private List<HttpMessageConverter<?>> partConverters = new ArrayList<HttpMessageConverter<?>>();
public FormHttpMessageConverter() {
this.supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
this.supportedMediaTypes.add(MediaType.MULTIPART_FORM_DATA);
this.partConverters.add(new ByteArrayHttpMessageConverter());
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false);
@@ -106,14 +99,6 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
}
/**
* 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.
*/
@@ -122,6 +107,14 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
this.partConverters = partConverters;
}
/**
* 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);
}
/**
* Sets the character set used for writing form data.
*/
@@ -129,34 +122,47 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
this.charset = charset;
}
public boolean canRead(Class<?> clazz, MediaType mediaType) {
if (!MultiValueMap.class.isAssignableFrom(clazz)) {
return false;
}
if (mediaType != null) {
return MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType);
}
else {
if (mediaType == null) {
return true;
}
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
// we can't read multipart
if (!supportedMediaType.equals(MediaType.MULTIPART_FORM_DATA) &&
supportedMediaType.includes(mediaType)) {
return true;
}
}
return false;
}
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
if (!MultiValueMap.class.isAssignableFrom(clazz)) {
return false;
}
if (mediaType != null) {
return mediaType.isCompatibleWith(MediaType.APPLICATION_FORM_URLENCODED) ||
mediaType.isCompatibleWith(MediaType.MULTIPART_FORM_DATA);
}
else {
if (mediaType == null || MediaType.ALL.equals(mediaType)) {
return true;
}
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
if (supportedMediaType.isCompatibleWith(mediaType)) {
return true;
}
}
return false;
}
/**
* Set the list of {@link MediaType} objects supported by this converter.
*/
public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes) {
this.supportedMediaTypes = supportedMediaTypes;
}
public List<MediaType> getSupportedMediaTypes() {
return Arrays.asList(MediaType.APPLICATION_FORM_URLENCODED, MediaType.MULTIPART_FORM_DATA);
return Collections.unmodifiableList(this.supportedMediaTypes);
}
public MultiValueMap<String, String> read(Class<? extends MultiValueMap<String, ?>> clazz,
@@ -188,7 +194,7 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
public void write(MultiValueMap<String, ?> map, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
if (!isMultipart(map, contentType)) {
writeForm((MultiValueMap<String, String>) map, outputMessage);
writeForm((MultiValueMap<String, String>) map, contentType, outputMessage);
}
else {
writeMultipart((MultiValueMap<String, Object>) map, outputMessage);
@@ -209,8 +215,17 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
return false;
}
private void writeForm(MultiValueMap<String, String> form, HttpOutputMessage outputMessage) throws IOException {
outputMessage.getHeaders().setContentType(MediaType.APPLICATION_FORM_URLENCODED);
private void writeForm(MultiValueMap<String, String> form, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException {
Charset charset;
if (contentType != null) {
outputMessage.getHeaders().setContentType(contentType);
charset = contentType.getCharSet() != null ? contentType.getCharSet() : this.charset;
}
else {
outputMessage.getHeaders().setContentType(MediaType.APPLICATION_FORM_URLENCODED);
charset = this.charset;
}
StringBuilder builder = new StringBuilder();
for (Iterator<String> nameIterator = form.keySet().iterator(); nameIterator.hasNext();) {
String name = nameIterator.next();
@@ -229,10 +244,13 @@ 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) throws IOException {
private void writeMultipart(MultiValueMap<String, Object> parts, HttpOutputMessage outputMessage)
throws IOException {
byte[] boundary = generateMultipartBoundary();
Map<String, String> parameters = Collections.singletonMap("boundary", new String(boundary, "US-ASCII"));
@@ -310,7 +328,8 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
/**
* Generate a multipart boundary.
* <p>Default implementation returns a random boundary. Can be overridden in subclasses.
* <p>The default implementation returns a random boundary.
* Can be overridden in subclasses.
*/
protected byte[] generateMultipartBoundary() {
byte[] boundary = new byte[rnd.nextInt(11) + 30];
@@ -321,9 +340,10 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
}
/**
* Return the filename of the given multipart part. This value will be used for the {@code Content-Disposition} header.
* <p>Default implementation returns {@link Resource#getFilename()} if the part is a {@code Resource}, and
* {@code null} in other cases. Can be overridden in subclasses.
* Return the filename of the given multipart part. This value will be used for the
* {@code Content-Disposition} header.
* <p>The default implementation returns {@link Resource#getFilename()} if the part is a
* {@code Resource}, and {@code null} in other cases. Can be overridden in subclasses.
* @param part the part to determine the file name for
* @return the filename, or {@code null} if not known
*/

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.
@@ -16,11 +16,20 @@
package org.springframework.http.server;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpHeaders;
@@ -35,14 +44,22 @@ import org.springframework.util.Assert;
*/
public class ServletServerHttpRequest implements ServerHttpRequest {
private static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded";
private static final String POST_METHOD = "POST";
private static final String PUT_METHOD = "PUT";
private static final String FORM_CHARSET = "UTF-8";
private final HttpServletRequest servletRequest;
private HttpHeaders headers;
/**
* Construct a new instance of the ServletServerHttpRequest based on the given {@link HttpServletRequest}
* @param servletRequest the HttpServletRequest
* Construct a new instance of the ServletServerHttpRequest based on the given {@link HttpServletRequest}.
* @param servletRequest the servlet request
*/
public ServletServerHttpRequest(HttpServletRequest servletRequest) {
Assert.notNull(servletRequest, "'servletRequest' must not be null");
@@ -50,15 +67,22 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
}
/**
* Returns the {@code HttpServletRequest} this object is based on.
*/
public HttpServletRequest getServletRequest() {
return this.servletRequest;
}
public HttpMethod getMethod() {
return HttpMethod.valueOf(this.servletRequest.getMethod());
}
public URI getURI() {
try {
return new URI(servletRequest.getScheme(), null, servletRequest.getServerName(),
servletRequest.getServerPort(), servletRequest.getRequestURI(),
servletRequest.getQueryString(), null);
return new URI(this.servletRequest.getScheme(), null, this.servletRequest.getServerName(),
this.servletRequest.getServerPort(), this.servletRequest.getRequestURI(),
this.servletRequest.getQueryString(), null);
}
catch (URISyntaxException ex) {
throw new IllegalStateException("Could not get HttpServletRequest URI: " + ex.getMessage(), ex);
@@ -70,7 +94,8 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
this.headers = new HttpHeaders();
for (Enumeration headerNames = this.servletRequest.getHeaderNames(); headerNames.hasMoreElements();) {
String headerName = (String) headerNames.nextElement();
for (Enumeration headerValues = this.servletRequest.getHeaders(headerName); headerValues.hasMoreElements();) {
for (Enumeration headerValues = this.servletRequest.getHeaders(headerName);
headerValues.hasMoreElements();) {
String headerValue = (String) headerValues.nextElement();
this.headers.add(headerName, headerValue);
}
@@ -80,7 +105,45 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
}
public InputStream getBody() throws IOException {
return this.servletRequest.getInputStream();
if (isFormSubmittal(this.servletRequest)) {
return getFormBody(this.servletRequest);
}
else {
return this.servletRequest.getInputStream();
}
}
private boolean isFormSubmittal(HttpServletRequest request) {
return FORM_CONTENT_TYPE.equals(request.getContentType()) &&
(POST_METHOD.equalsIgnoreCase(request.getMethod()) || PUT_METHOD.equalsIgnoreCase(request.getMethod()));
}
private InputStream getFormBody(HttpServletRequest request) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Writer writer = new OutputStreamWriter(bos, FORM_CHARSET);
Map<String, String[]> form = request.getParameterMap();
for (Iterator<String> nameIterator = form.keySet().iterator(); nameIterator.hasNext();) {
String name = nameIterator.next();
List<String> values = Arrays.asList(form.get(name));
for (Iterator<String> valueIterator = values.iterator(); valueIterator.hasNext();) {
String value = valueIterator.next();
writer.write(URLEncoder.encode(name, FORM_CHARSET));
if (value != null) {
writer.write('=');
writer.write(URLEncoder.encode(value, FORM_CHARSET));
if (valueIterator.hasNext()) {
writer.write('&');
}
}
}
if (nameIterator.hasNext()) {
writer.append('&');
}
}
writer.flush();
return new ByteArrayInputStream(bos.toByteArray());
}
}

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.
@@ -43,7 +43,7 @@ public class ServletServerHttpResponse implements ServerHttpResponse {
/**
* Construct a new instance of the ServletServerHttpResponse based on the given {@link HttpServletResponse}.
* @param servletResponse the HTTP Servlet response
* @param servletResponse the servlet response
*/
public ServletServerHttpResponse(HttpServletResponse servletResponse) {
Assert.notNull(servletResponse, "'servletResponse' must not be null");
@@ -51,12 +51,19 @@ public class ServletServerHttpResponse implements ServerHttpResponse {
}
/**
* Return the {@code HttpServletResponse} this object is based on.
*/
public HttpServletResponse getServletResponse() {
return this.servletResponse;
}
public void setStatusCode(HttpStatus status) {
this.servletResponse.setStatus(status.value());
}
public HttpHeaders getHeaders() {
return headersWritten ? HttpHeaders.readOnlyHttpHeaders(headers) : this.headers;
return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
}
public OutputStream getBody() throws IOException {

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.
@@ -21,6 +21,7 @@ import javax.servlet.ServletRequest;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.validation.BindException;
import org.springframework.web.multipart.MultipartRequest;
import org.springframework.web.util.WebUtils;
/**
* Special {@link org.springframework.validation.DataBinder} to perform data binding
@@ -103,8 +104,8 @@ public class ServletRequestDataBinder extends WebDataBinder {
*/
public void bind(ServletRequest request) {
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
if (request instanceof MultipartRequest) {
MultipartRequest multipartRequest = (MultipartRequest) request;
MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
if (multipartRequest != null) {
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
}
doBind(mpvs);

View File

@@ -1,11 +1,11 @@
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,
@@ -20,10 +20,6 @@ import java.security.Principal;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestWrapper;
import javax.servlet.ServletResponse;
import javax.servlet.ServletResponseWrapper;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@@ -31,6 +27,7 @@ import javax.servlet.http.HttpSession;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.util.WebUtils;
/**
* {@link WebRequest} adapter for an {@link javax.servlet.http.HttpServletRequest}.
@@ -40,10 +37,16 @@ import org.springframework.util.StringUtils;
*/
public class ServletWebRequest extends ServletRequestAttributes implements NativeWebRequest {
private static final String HEADER_ETAG = "ETag";
private static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since";
private static final String HEADER_IF_NONE_MATCH = "If-None-Match";
private static final String HEADER_LAST_MODIFIED = "Last-Modified";
private static final String METHOD_GET = "GET";
private HttpServletResponse response;
@@ -86,40 +89,12 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
@SuppressWarnings("unchecked")
public <T> T getNativeRequest(Class<T> requiredType) {
if (requiredType != null) {
ServletRequest request = getRequest();
while (request != null) {
if (requiredType.isInstance(request)) {
return (T) request;
}
else if (request instanceof ServletRequestWrapper) {
request = ((ServletRequestWrapper) request).getRequest();
}
else {
request = null;
}
}
}
return null;
return WebUtils.getNativeRequest(getRequest(), requiredType);
}
@SuppressWarnings("unchecked")
public <T> T getNativeResponse(Class<T> requiredType) {
if (requiredType != null) {
ServletResponse response = getResponse();
while (response != null) {
if (requiredType.isInstance(response)) {
return (T) response;
}
else if (response instanceof ServletResponseWrapper) {
response = ((ServletResponseWrapper) response).getResponse();
}
else {
response = null;
}
}
}
return null;
return WebUtils.getNativeResponse(getResponse(), requiredType);
}
@@ -186,7 +161,7 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
long ifModifiedSince = getRequest().getDateHeader(HEADER_IF_MODIFIED_SINCE);
this.notModified = (ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000));
if (this.response != null) {
if (this.notModified && "GET".equals(getRequest().getMethod())) {
if (this.notModified && METHOD_GET.equals(getRequest().getMethod())) {
this.response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
else {

View File

@@ -69,7 +69,6 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
if (logger.isTraceEnabled()) {
logger.trace("ETag [" + responseETag + "] equal to If-None-Match, sending 304");
}
response.setContentLength(0);
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
else {
@@ -89,8 +88,8 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
}
private void copyBodyToResponse(byte[] body, HttpServletResponse response) throws IOException {
response.setContentLength(body.length);
if (body.length > 0) {
response.setContentLength(body.length);
FileCopyUtils.copy(body, response.getOutputStream());
}
}
@@ -113,7 +112,7 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
/**
* Generate the ETag header value from the given response body byte array.
* <p>The default implementation generates an MD5 hash.
* @param bytes the response bdoy as byte array
* @param bytes the response body as byte array
* @return the ETag header value
* @see org.springframework.util.DigestUtils
*/
@@ -168,6 +167,10 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
this.statusCode = sc;
}
@Override
public void setContentLength(int len) {
}
@Override
public ServletOutputStream getOutputStream() {
return this.outputStream;

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.
@@ -23,6 +23,9 @@ import java.util.Map;
import java.util.TreeMap;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestWrapper;
import javax.servlet.ServletResponse;
import javax.servlet.ServletResponseWrapper;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -366,6 +369,48 @@ public abstract class WebUtils {
}
/**
* Return an appropriate request object of the specified type, if available,
* unwrapping the given request as far as necessary.
* @param request the servlet request to introspect
* @param requiredType the desired type of request object
* @return the matching request object, or <code>null</code> if none
* of that type is available
*/
@SuppressWarnings("unchecked")
public static <T> T getNativeRequest(ServletRequest request, Class<T> requiredType) {
if (requiredType != null) {
if (requiredType.isInstance(request)) {
return (T) request;
}
else if (request instanceof ServletRequestWrapper) {
return getNativeRequest(((ServletRequestWrapper) request).getRequest(), requiredType);
}
}
return null;
}
/**
* Return an appropriate response object of the specified type, if available,
* unwrapping the given response as far as necessary.
* @param response the servlet response to introspect
* @param requiredType the desired type of response object
* @return the matching response object, or <code>null</code> if none
* of that type is available
*/
@SuppressWarnings("unchecked")
public static <T> T getNativeResponse(ServletResponse response, Class<T> requiredType) {
if (requiredType != null) {
if (requiredType.isInstance(response)) {
return (T) response;
}
else if (response instanceof ServletResponseWrapper) {
return getNativeResponse(((ServletResponseWrapper) response).getResponse(), requiredType);
}
}
return null;
}
/**
* Determine whether the given request is an include request,
* that is, not a top-level HTTP request coming in from the outside.

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,6 +44,8 @@ import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import static org.junit.Assert.*;
/**
* @author Arjen Poutsma
*/
@@ -60,13 +61,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 +78,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 +98,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 +125,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 +160,7 @@ public class FormHttpMessageConverterTests {
}
private static class MockHttpOutputMessageRequestContext implements RequestContext {
private final MockHttpOutputMessage outputMessage;
private MockHttpOutputMessageRequestContext(MockHttpOutputMessage outputMessage) {
@@ -182,5 +186,4 @@ public class FormHttpMessageConverterTests {
}
}
}