Up-to-date and expanded coverage on preparing URIs

Issue: SPR-16422
This commit is contained in:
Rossen Stoyanchev 2018-02-15 23:25:52 -05:00
parent 9801afb85d
commit aa4bcedad3
3 changed files with 182 additions and 80 deletions

View File

@ -1139,12 +1139,9 @@ other method arguments.
[[rest-resttemplate-uri]] [[rest-resttemplate-uri]]
===== Working with the URI ===== Working with the URI
For each of the main HTTP methods, the `RestTemplate` provides variants that either take For each of the main HTTP methods, the `RestTemplate` provides two variants that take
a String URI or `java.net.URI` as the first argument. either a String URI template, or `java.net.URI` as the first argument. When using a
String URI template, encoding is automatically applied:
The String URI variants accept template arguments as a String variable-length argument
or as a `Map<String,String>`. They also assume the URL String is not encoded and needs
to be encoded. For example the following:
[source,java,indent=0] [source,java,indent=0]
[subs="verbatim,quotes"] [subs="verbatim,quotes"]
@ -1152,39 +1149,11 @@ to be encoded. For example the following:
restTemplate.getForObject("http://example.com/hotel list", String.class); restTemplate.getForObject("http://example.com/hotel list", String.class);
---- ----
will perform a GET on `http://example.com/hotel%20list`. That means if the input URL The resulting target URI is "http://example.com/hotel%20list". Alternatively you can
String is already encoded, it will be encoded twice -- i.e. provide an already prepared `java.net.URI` and that will be used as is.
`http://example.com/hotel%20list` will become `http://example.com/hotel%2520list`. If For more information on preparing URIs, or customizing how the `RestTemplate` expands
this is not the intended effect, use the `java.net.URI` method variant, which assumes URI templates, see <<web.adoc#mvc-uri-building,URI Links>> in the "Web Servlet" section.
the URL is already encoded is also generally useful if you want to reuse a single (fully
expanded) `URI` multiple times.
The `UriComponentsBuilder` class can be used to build and encode the `URI` including
support for URI templates. For example you can start with a URL String:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
UriComponents uriComponents = UriComponentsBuilder.fromUriString(
"http://example.com/hotels/{hotel}/bookings/{booking}").build()
.expand("42", "21")
.encode();
URI uri = uriComponents.toUri();
----
Or specify each URI component individually:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
UriComponents uriComponents = UriComponentsBuilder.newInstance()
.scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build()
.expand("42", "21")
.encode();
URI uri = uriComponents.toUri();
----
[[rest-template-headers]] [[rest-template-headers]]
===== Dealing with request and response headers ===== Dealing with request and response headers

View File

@ -0,0 +1,145 @@
[[web-uricomponents]]
= UriComponents
`UriComponents` is comparable to `java.net.URI`. However it comes with a dedicated
`UriComponentsBuilder` and support URI template variables:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
String uriTemplate = "http://example.com/hotels/{hotel}";
UriComponents uriComponents = UriComponentsBuilder.fromUriString(uriTemplate) // <1>
.queryParam("q", "{q}") // <2>
.build(); // <3>
URI uri = uriComponents.expand("Westin", "123").encode().toUri(); // <4>
----
<1> Static factory method with a URI template.
<2> Add or replace URI components.
<3> Build `UriComponents`.
<4> Expand URI variables, encode, and obtain the `URI`.
The above can be done as a single chain, and with a shortcut:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
String uriTemplate = "http://example.com/hotels/{hotel}";
URI uri = UriComponentsBuilder.fromUriString(uriTemplate)
.queryParam("q", "{q}")
.buildAndExpand("Westin", "123")
.encode()
.toUri();
----
[[web-uribuilder]]
= UriBuilder
<<web-uricomponents,UriComponentsBuilder>> is an implementation of `UriBuilder`. Together
`UriBuilderFactory` and `UriBuilder` provide a pluggable mechanism for building a URI
from a URI template, as well as a way to share common properties such as a base URI,
encoding strategy, and others.
Both the `RestTemplate` and the `WebClient` can be configured with a `UriBuilderFactory`,
in order to customize how URIs are created from URI templates. The default implementation
relies on `UriComponentsBuilder` internally and provides options to a common base URI,
an alternative encoding mode strategy, and more.
An example of configuring the `RestTemplate`:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
String baseUrl = "http://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);
----
Examples of configuring the `WebClient`:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
String baseUrl = "http://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
// Configure the UriBuilderFactory..
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
// Or use shortcut on builder..
WebClient client = WebClient.builder().baseUrl(baseUrl).build();
// Or use create shortcut...
WebClient client = WebClient.create(baseUrl);
----
You can also use `DefaultUriBuilderFactory` directly, as you would `UriComponentsBuilder`.
The main difference is that `DefaultUriBuilderFactory` is stateful and can be re-used to
prepare many URLs, sharing common configuration, such as a base URL, while
`UriComponentsBuilder` is stateless and per URI.
An example of using the `DefaultUriBuilderFactory`:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
String baseUrl = "http://example.com";
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);
URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123"); // encoding strategy applied..
----
[[web-uri-encoding]]
= URI Encoding
The default way of encoding URIs in `UriComponents` works as follows:
. URI variables are expanded.
. URI components are encoded individually.
For each URI component, percent encoding is applied to all illegal characters, which
includes non-US-ASCII characters, and other characters that are illegal within a given
URI component type, as defined in RFC 3986.
[TIP]
====
The encoding in `UriComponents` is comparable to the multi-argument constructor of
`java.net.URI`, as described in the "Escaped octets, quotation, encoding, and decoding"
section of its class-level Javadoc.
====
This default way of encoding *does not* encode all characters with reserved meaning, but
only the ones that are illegal within a given URI component. If this is not what you
expect you can use an alternative.
When using <<web-uribuilder,DefaultUriBuilderFactory>> you can switch to an alternative
encoding strategy:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
String baseUrl = "http://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
factory.setEncodingMode(EncodingMode.VALUES_ONLY);
// ...
----
The above encoding strategy applies `UriUtils.encode(String, Charset)` to each URI
variable value prior to expanding it. Effectively it encodes all characters with reserved
meaning, therefore ensuring that expanded URI variable do not have any impact on the
structure or meaning of the URI.

View File

@ -2881,44 +2881,25 @@ Javadoc for more details.
[[mvc-uri-building]] [[mvc-uri-building]]
== URI Links == URI Links
Spring MVC provides a mechanism for building and encoding a URI using This section describes various options available in the Spring Framework to prepare URIs.
`UriComponentsBuilder` and `UriComponents`.
For example you can expand and encode a URI template string:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
UriComponents uriComponents = UriComponentsBuilder.fromUriString(
"http://example.com/hotels/{hotel}/bookings/{booking}").build();
URI uri = uriComponents.expand("42", "21").encode().toUri(); include::web-uris.adoc[leveloffset=+2]
----
Note that `UriComponents` is immutable and the `expand()` and `encode()` operations
return new instances if necessary.
You can also expand and encode using individual URI components:
[source,java,indent=0] [[mvc-servleturicomponentsbuilder]]
[subs="verbatim,quotes"] === Servlet request relative
----
UriComponents uriComponents = UriComponentsBuilder.newInstance()
.scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build()
.expand("42", "21")
.encode();
----
In a Servlet environment the `ServletUriComponentsBuilder` subclass provides static You can use `ServletUriComponentsBuilder` to create URIs relative to the current request:
factory methods to copy available URL information from a Servlet requests:
[source,java,indent=0] [source,java,indent=0]
[subs="verbatim,quotes"] [subs="verbatim,quotes"]
---- ----
HttpServletRequest request = ... HttpServletRequest request = ...
// Re-use host, scheme, port, path and query string // Re-uses host, scheme, port, path and query string...
// Replace the "accountId" query param
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request) ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request)
.replaceQueryParam("accountId", "{id}").build() .replaceQueryParam("accountId", "{id}").build()
@ -2926,48 +2907,44 @@ factory methods to copy available URL information from a Servlet requests:
.encode(); .encode();
---- ----
Alternatively, you may choose to copy a subset of the available information up to and You can create URIs relative to the context path:
including the context path:
[source,java,indent=0] [source,java,indent=0]
[subs="verbatim,quotes"] [subs="verbatim,quotes"]
---- ----
// Re-use host, port and context path // Re-uses host, port and context path...
// Append "/accounts" to the path
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request) ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request)
.path("/accounts").build() .path("/accounts").build()
---- ----
Or in cases where the `DispatcherServlet` is mapped by name (e.g. `/main/{asterisk}`), you can You can create URIs relative to a Servlet (e.g. `/main/{asterisk}`):
also have the literal part of the servlet mapping included:
[source,java,indent=0] [source,java,indent=0]
[subs="verbatim,quotes"] [subs="verbatim,quotes"]
---- ----
// Re-use host, port, context path // Re-uses host, port, context path, and Servlet prefix...
// Append the literal part of the servlet mapping to the path
// Append "/accounts" to the path
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request) ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request)
.path("/accounts").build() .path("/accounts").build()
---- ----
[TIP] [CAUTION]
==== ====
Both `ServletUriComponentsBuilder` and `MvcUriComponentsBuilder` detect, extract, and use `ServletUriComponentsBuilder` detects and uses information from the "Forwarded",
information from the "Forwarded" header, or from "X-Forwarded-Host", "X-Forwarded-Port", "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" headers, so the resulting
and "X-Forwarded-Proto" if "Forwarded" is not present, so that the resulting links reflect links reflect the original request. You need to ensure that your application is behind
the original request. Note that you can also use the a trusted proxy which filters out such headers coming from outside. Also consider using
<<filters-forwarded-headers,ForwardedHeaderFilter>> to the same once, globally. the <<filters-forwarded-headers,ForwardedHeaderFilter>> which processes such headers once
per request, and also provides an option to remove and ignore such headers.
==== ====
[[mvc-links-to-controllers]] [[mvc-links-to-controllers]]
=== Links to Controllers === Links to controllers
Spring MVC also provides a mechanism for building links to controller methods. For example, given: Spring MVC provides a mechanism to prepare links to controller methods. For example:
[source,java,indent=0] [source,java,indent=0]
[subs="verbatim,quotes"] [subs="verbatim,quotes"]
@ -3036,6 +3013,17 @@ with a base URL and then use the instance-based "withXxx" methods. For example:
URI uri = uriComponents.encode().toUri(); URI uri = uriComponents.encode().toUri();
---- ----
[CAUTION]
====
`MvcUriComponentsBuilder` detects and uses information from the "Forwarded",
"X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" headers, so the resulting
links reflect the original request. You need to ensure that your application is behind
a trusted proxy which filters out such headers coming from outside. Also consider using
the <<filters-forwarded-headers,ForwardedHeaderFilter>> which processes such headers once
per request, and also provides an option to remove and ignore such headers.
====
[[mvc-links-to-controllers-from-views]] [[mvc-links-to-controllers-from-views]]