SPR-6946 - RestTemplate should not encode fragments (#'s)

This commit is contained in:
Arjen Poutsma
2010-03-05 11:40:52 +00:00
parent 54d0346084
commit c91ff130d5
4 changed files with 199 additions and 67 deletions

View File

@@ -17,9 +17,10 @@
package org.springframework.web.client;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -36,11 +37,12 @@ import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.util.UriTemplate;
import org.springframework.web.util.UriUtils;
/**
* <strong>The central class for client-side HTTP access.</strong> It simplifies communication with HTTP servers, and
@@ -302,7 +304,7 @@ public class RestTemplate extends HttpAccessor implements RestOperations {
public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback,
ResponseExtractor<T> responseExtractor, Object... urlVariables) throws RestClientException {
UriTemplate uriTemplate = new UriTemplate(url);
UriTemplate uriTemplate = new HttpUrlTemplate(url);
URI expanded = uriTemplate.expand(urlVariables);
return doExecute(expanded, method, requestCallback, responseExtractor);
}
@@ -310,7 +312,7 @@ public class RestTemplate extends HttpAccessor implements RestOperations {
public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback,
ResponseExtractor<T> responseExtractor, Map<String, ?> urlVariables) throws RestClientException {
UriTemplate uriTemplate = new UriTemplate(url);
UriTemplate uriTemplate = new HttpUrlTemplate(url);
URI expanded = uriTemplate.expand(urlVariables);
return doExecute(expanded, method, requestCallback, responseExtractor);
}
@@ -489,4 +491,29 @@ public class RestTemplate extends HttpAccessor implements RestOperations {
}
}
/**
* HTTP-specific subclass of UriTemplate, overriding the encode method.
*/
private static class HttpUrlTemplate extends UriTemplate {
public HttpUrlTemplate(String uriTemplate) {
super(uriTemplate);
}
@Override
protected URI encodeUri(String uri) {
try {
String encoded = UriUtils.encodeHttpUrl(uri, "UTF-8");
return new URI(encoded);
}
catch (UnsupportedEncodingException ex) {
// should not happen, UTF-8 is always supported
throw new IllegalStateException(ex);
}
catch (URISyntaxException ex) {
throw new IllegalArgumentException("Could not create HTTP URL from [" + uri + "]: " + ex, ex);
}
}
}
}

View File

@@ -70,7 +70,7 @@ public class UriTemplate {
* Return the names of the variables in the template, in order.
* @return the template variable names
*/
public final List<String> getVariableNames() {
public List<String> getVariableNames() {
return this.variableNames;
}
@@ -172,7 +172,15 @@ public class UriTemplate {
return this.uriTemplate;
}
private static URI encodeUri(String uri) {
/**
* Encodes the given String as URL.
*
* <p>Defaults to {@link UriUtils#encodeUri(String, String)}.
*
* @param uri the URI to encode
* @return the encoded URI
*/
protected URI encodeUri(String uri) {
try {
String encoded = UriUtils.encodeUri(uri, "UTF-8");
return new URI(encoded);
@@ -186,7 +194,6 @@ public class UriTemplate {
}
}
/**
* Static inner class to parse uri template strings into a matching regular expression.
*/

View File

@@ -36,13 +36,13 @@ import org.springframework.util.Assert;
* </ul>
*
* @author Arjen Poutsma
* @since 3.0
* @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>
* @since 3.0
*/
public abstract class UriUtils {
private static final BitSet SCHEME;
private static final BitSet USER_INFO;
private static final BitSet HOST;
@@ -61,6 +61,8 @@ public abstract class UriUtils {
private static final String SCHEME_PATTERN = "([^:/?#]+):";
private static final String HTTP_PATTERN = "(http|https):";
private static final String USERINFO_PATTERN = "([^@/]*)";
private static final String HOST_PATTERN = "([^/?#:]*)";
@@ -71,15 +73,16 @@ public abstract class UriUtils {
private static final String QUERY_PATTERN = "([^#]*)";
private static final String FRAGMENT_PATTERN = "(.*)";
private static final String LAST_PATTERN = "(.*)";
// Regex patterns that matches URIs. See RFC 3986, appendix B
private static final Pattern URI_PATTERN =
Pattern.compile("^(" + SCHEME_PATTERN + ")?" +
"(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" + ")?"
+ PATH_PATTERN +
"(\\?" + QUERY_PATTERN + ")?" +
"(#" + FRAGMENT_PATTERN + ")?");
private static final Pattern URI_PATTERN = Pattern.compile(
"^(" + SCHEME_PATTERN + ")?" + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN +
")?" + ")?" + PATH_PATTERN + "(\\?" + QUERY_PATTERN + ")?" + "(#" + LAST_PATTERN + ")?");
private static final Pattern HTTP_URL_PATTERN = Pattern.compile(
"^" + HTTP_PATTERN + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" +
")?" + PATH_PATTERN + "(\\?" + LAST_PATTERN + ")?");
static {
// variable names refer to RFC 3986, appendix A
@@ -181,10 +184,10 @@ public abstract class UriUtils {
}
/**
* Encodes the given source URI into an encoded String. All various URI components are encoded according to
* their respective valid character sets.
* Encodes the given source URI into an encoded String. All various URI components are encoded according to their
* respective valid character sets.
*
* @param uri the URI to be encoded
* @param uri the URI to be encoded
* @param encoding the character encoding to encode to
* @return the encoded URI
* @throws IllegalArgumentException when the given uri parameter is not a valid URI
@@ -204,50 +207,115 @@ public abstract class UriUtils {
String query = m.group(11);
String fragment = m.group(13);
StringBuilder sb = new StringBuilder();
if (scheme != null) {
sb.append(encodeScheme(scheme, encoding));
sb.append(':');
}
if (authority != null) {
sb.append("//");
if (userinfo != null) {
sb.append(encodeUserInfo(userinfo, encoding));
sb.append('@');
}
if (host != null) {
sb.append(encodeHost(host, encoding));
}
if (port != null) {
sb.append(':');
sb.append(encodePort(port, encoding));
}
}
sb.append(encodePath(path, encoding));
if (query != null) {
sb.append('?');
sb.append(encodeQuery(query, encoding));
}
if (fragment != null) {
sb.append('#');
sb.append(encodeFragment(fragment, encoding));
}
return sb.toString();
} else {
return encodeUriComponents(scheme, authority, userinfo, host, port, path, query, fragment, encoding);
}
else {
throw new IllegalArgumentException("[" + uri + "] is not a valid URI");
}
}
/**
* Encodes the given HTTP URI into an encoded String. All various URI components are encoded according to their
* respective valid character sets.
*
* <p><strong>Note</strong> that this method does not support fragments ({@code #}), as these are not supposed to be
* sent to the server, but retained by the client.
*
* @param httpUrl the HTTP URL to be encoded
* @param encoding the character encoding to encode to
* @return the encoded URL
* @throws IllegalArgumentException when the given uri parameter is not a valid URI
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeHttpUrl(String httpUrl, String encoding) throws UnsupportedEncodingException {
Assert.notNull(httpUrl, "'httpUrl' must not be null");
Assert.hasLength(encoding, "'encoding' must not be empty");
Matcher m = HTTP_URL_PATTERN.matcher(httpUrl);
if (m.matches()) {
String scheme = m.group(1);
String authority = m.group(2);
String userinfo = m.group(4);
String host = m.group(5);
String portString = m.group(7);
String path = m.group(8);
String query = m.group(10);
return encodeUriComponents(scheme, authority, userinfo, host, portString, path, query, null, encoding);
}
else {
throw new IllegalArgumentException("[" + httpUrl + "] is not a valid HTTP URL");
}
}
/**
* Encodes the given source URI components into an encoded String. All various URI components are optional, but encoded according
* to their respective valid character sets.
*
* @param scheme the scheme
* @param authority the authority
* @param userinfo the user info
* @param host the host
* @param port the port
* @param path the path
* @param query the query
* @param fragment the fragment
* @param encoding the character encoding to encode to
* @return the encoded URI
* @throws IllegalArgumentException when the given uri parameter is not a valid URI
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeUriComponents(String scheme,
String authority,
String userinfo,
String host,
String port,
String path,
String query,
String fragment,
String encoding) throws UnsupportedEncodingException {
Assert.hasLength(encoding, "'encoding' must not be empty");
StringBuilder sb = new StringBuilder();
if (scheme != null) {
sb.append(encodeScheme(scheme, encoding));
sb.append(':');
}
if (authority != null) {
sb.append("//");
if (userinfo != null) {
sb.append(encodeUserInfo(userinfo, encoding));
sb.append('@');
}
if (host != null) {
sb.append(encodeHost(host, encoding));
}
if (port != null) {
sb.append(':');
sb.append(encodePort(port, encoding));
}
}
sb.append(encodePath(path, encoding));
if (query != null) {
sb.append('?');
sb.append(encodeQuery(query, encoding));
}
if (fragment != null) {
sb.append('#');
sb.append(encodeFragment(fragment, encoding));
}
return sb.toString();
}
/**
* Encodes the given URI scheme.
*
* @param scheme the scheme to be encoded
* @param scheme the scheme to be encoded
* @param encoding the character encoding to encode to
* @return the encoded scheme
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
@@ -271,7 +339,7 @@ public abstract class UriUtils {
/**
* Encodes the given URI host.
*
* @param host the host to be encoded
* @param host the host to be encoded
* @param encoding the character encoding to encode to
* @return the encoded host
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
@@ -283,7 +351,7 @@ public abstract class UriUtils {
/**
* Encodes the given URI port.
*
* @param port the port to be encoded
* @param port the port to be encoded
* @param encoding the character encoding to encode to
* @return the encoded port
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
@@ -295,7 +363,7 @@ public abstract class UriUtils {
/**
* Encodes the given URI path.
*
* @param path the path to be encoded
* @param path the path to be encoded
* @param encoding the character encoding to encode to
* @return the encoded path
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
@@ -307,7 +375,7 @@ public abstract class UriUtils {
/**
* Encodes the given URI path segment.
*
* @param segment the segment to be encoded
* @param segment the segment to be encoded
* @param encoding the character encoding to encode to
* @return the encoded segment
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
@@ -319,7 +387,7 @@ public abstract class UriUtils {
/**
* Encodes the given URI query.
*
* @param query the query to be encoded
* @param query the query to be encoded
* @param encoding the character encoding to encode to
* @return the encoded query
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
@@ -332,7 +400,7 @@ public abstract class UriUtils {
* Encodes the given URI query parameter.
*
* @param queryParam the query parameter to be encoded
* @param encoding the character encoding to encode to
* @param encoding the character encoding to encode to
* @return the encoded query parameter
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
@@ -352,7 +420,8 @@ public abstract class UriUtils {
return encode(fragment, encoding, FRAGMENT);
}
private static String encode(String source, String encoding, BitSet notEncoded) throws UnsupportedEncodingException {
private static String encode(String source, String encoding, BitSet notEncoded)
throws UnsupportedEncodingException {
Assert.notNull(source, "'source' must not be null");
Assert.hasLength(encoding, "'encoding' must not be empty");
ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length() * 2);
@@ -388,8 +457,9 @@ public abstract class UriUtils {
* <li>A sequence "<code>%<i>xy</i></code>" is interpreted as a hexadecimal
* representation of the character.
* </ul>
* @param source
* @param encoding
*
* @param source the source string
* @param encoding the encoding
* @return the decoded URI
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
* @see java.net.URLDecoder#decode(String, String)