Skip to content

Commit

Permalink
Up-to-date and expanded coverage on preparing URIs
Browse files Browse the repository at this point in the history
Issue: SPR-16422
  • Loading branch information
rstoyanchev committed Feb 16, 2018
1 parent 9801afb commit aa4bced
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 80 deletions.
45 changes: 7 additions & 38 deletions src/docs/asciidoc/integration.adoc
Expand Up @@ -1139,52 +1139,21 @@ other method arguments.
[[rest-resttemplate-uri]]
===== Working with the URI

For each of the main HTTP methods, the `RestTemplate` provides variants that either take
a String URI or `java.net.URI` as the first argument.

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:
For each of the main HTTP methods, the `RestTemplate` provides two variants that take
either a String URI template, or `java.net.URI` as the first argument. When using a
String URI template, encoding is automatically applied:

[source,java,indent=0]
[subs="verbatim,quotes"]
----
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
String is already encoded, it will be encoded twice -- i.e.
`http://example.com/hotel%20list` will become `http://example.com/hotel%2520list`. If
this is not the intended effect, use the `java.net.URI` method variant, which assumes
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:
The resulting target URI is "http://example.com/hotel%20list". Alternatively you can
provide an already prepared `java.net.URI` and that will be used as is.
For more information on preparing URIs, or customizing how the `RestTemplate` expands
URI templates, see <<web.adoc#mvc-uri-building,URI Links>> in the "Web Servlet" section.

[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]]
===== Dealing with request and response headers
Expand Down
145 changes: 145 additions & 0 deletions src/docs/asciidoc/web/web-uris.adoc
@@ -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.

72 changes: 30 additions & 42 deletions src/docs/asciidoc/web/webmvc.adoc
Expand Up @@ -2881,93 +2881,70 @@ Javadoc for more details.
[[mvc-uri-building]]
== URI Links

Spring MVC provides a mechanism for building and encoding a URI using
`UriComponentsBuilder` and `UriComponents`.
This section describes various options available in the Spring Framework to prepare URIs.

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]
[subs="verbatim,quotes"]
----
UriComponents uriComponents = UriComponentsBuilder.newInstance()
.scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build()
.expand("42", "21")
.encode();
----
[[mvc-servleturicomponentsbuilder]]
=== Servlet request relative

In a Servlet environment the `ServletUriComponentsBuilder` subclass provides static
factory methods to copy available URL information from a Servlet requests:
You can use `ServletUriComponentsBuilder` to create URIs relative to the current request:

[source,java,indent=0]
[subs="verbatim,quotes"]
----
HttpServletRequest request = ...
// Re-use host, scheme, port, path and query string
// Replace the "accountId" query param
// Re-uses host, scheme, port, path and query string...
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request)
.replaceQueryParam("accountId", "{id}").build()
.expand("123")
.encode();
----

Alternatively, you may choose to copy a subset of the available information up to and
including the context path:
You can create URIs relative to the context path:

[source,java,indent=0]
[subs="verbatim,quotes"]
----
// Re-use host, port and context path
// Append "/accounts" to the path
// Re-uses host, port and context path...
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request)
.path("/accounts").build()
----

Or in cases where the `DispatcherServlet` is mapped by name (e.g. `/main/{asterisk}`), you can
also have the literal part of the servlet mapping included:
You can create URIs relative to a Servlet (e.g. `/main/{asterisk}`):

[source,java,indent=0]
[subs="verbatim,quotes"]
----
// Re-use host, port, context path
// Append the literal part of the servlet mapping to the path
// Append "/accounts" to the path
// Re-uses host, port, context path, and Servlet prefix...
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request)
.path("/accounts").build()
----

[TIP]
[CAUTION]
====
Both `ServletUriComponentsBuilder` and `MvcUriComponentsBuilder` detect, extract, and use
information from the "Forwarded" header, or from "X-Forwarded-Host", "X-Forwarded-Port",
and "X-Forwarded-Proto" if "Forwarded" is not present, so that the resulting links reflect
the original request. Note that you can also use the
<<filters-forwarded-headers,ForwardedHeaderFilter>> to the same once, globally.
`ServletUriComponentsBuilder` 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]]
=== 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]
[subs="verbatim,quotes"]
Expand Down Expand Up @@ -3036,6 +3013,17 @@ with a base URL and then use the instance-based "withXxx" methods. For example:
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]]
Expand Down

0 comments on commit aa4bced

Please sign in to comment.