Skip to content

Commit aa4bced

Browse files
committed
Up-to-date and expanded coverage on preparing URIs
Issue: SPR-16422
1 parent 9801afb commit aa4bced

File tree

3 files changed

+182
-80
lines changed

3 files changed

+182
-80
lines changed

src/docs/asciidoc/integration.adoc

Lines changed: 7 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,52 +1139,21 @@ other method arguments.
11391139
[[rest-resttemplate-uri]]
11401140
===== Working with the URI
11411141

1142-
For each of the main HTTP methods, the `RestTemplate` provides variants that either take
1143-
a String URI or `java.net.URI` as the first argument.
1144-
1145-
The String URI variants accept template arguments as a String variable-length argument
1146-
or as a `Map<String,String>`. They also assume the URL String is not encoded and needs
1147-
to be encoded. For example the following:
1142+
For each of the main HTTP methods, the `RestTemplate` provides two variants that take
1143+
either a String URI template, or `java.net.URI` as the first argument. When using a
1144+
String URI template, encoding is automatically applied:
11481145

11491146
[source,java,indent=0]
11501147
[subs="verbatim,quotes"]
11511148
----
11521149
restTemplate.getForObject("http://example.com/hotel list", String.class);
11531150
----
11541151

1155-
will perform a GET on `http://example.com/hotel%20list`. That means if the input URL
1156-
String is already encoded, it will be encoded twice -- i.e.
1157-
`http://example.com/hotel%20list` will become `http://example.com/hotel%2520list`. If
1158-
this is not the intended effect, use the `java.net.URI` method variant, which assumes
1159-
the URL is already encoded is also generally useful if you want to reuse a single (fully
1160-
expanded) `URI` multiple times.
1161-
1162-
The `UriComponentsBuilder` class can be used to build and encode the `URI` including
1163-
support for URI templates. For example you can start with a URL String:
1164-
1165-
[source,java,indent=0]
1166-
[subs="verbatim,quotes"]
1167-
----
1168-
UriComponents uriComponents = UriComponentsBuilder.fromUriString(
1169-
"http://example.com/hotels/{hotel}/bookings/{booking}").build()
1170-
.expand("42", "21")
1171-
.encode();
1172-
1173-
URI uri = uriComponents.toUri();
1174-
----
1175-
1176-
Or specify each URI component individually:
1152+
The resulting target URI is "http://example.com/hotel%20list". Alternatively you can
1153+
provide an already prepared `java.net.URI` and that will be used as is.
1154+
For more information on preparing URIs, or customizing how the `RestTemplate` expands
1155+
URI templates, see <<web.adoc#mvc-uri-building,URI Links>> in the "Web Servlet" section.
11771156

1178-
[source,java,indent=0]
1179-
[subs="verbatim,quotes"]
1180-
----
1181-
UriComponents uriComponents = UriComponentsBuilder.newInstance()
1182-
.scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build()
1183-
.expand("42", "21")
1184-
.encode();
1185-
1186-
URI uri = uriComponents.toUri();
1187-
----
11881157

11891158
[[rest-template-headers]]
11901159
===== Dealing with request and response headers

src/docs/asciidoc/web/web-uris.adoc

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
2+
[[web-uricomponents]]
3+
= UriComponents
4+
5+
`UriComponents` is comparable to `java.net.URI`. However it comes with a dedicated
6+
`UriComponentsBuilder` and support URI template variables:
7+
8+
[source,java,indent=0]
9+
[subs="verbatim,quotes"]
10+
----
11+
String uriTemplate = "http://example.com/hotels/{hotel}";
12+
13+
UriComponents uriComponents = UriComponentsBuilder.fromUriString(uriTemplate) // <1>
14+
.queryParam("q", "{q}") // <2>
15+
.build(); // <3>
16+
17+
URI uri = uriComponents.expand("Westin", "123").encode().toUri(); // <4>
18+
----
19+
<1> Static factory method with a URI template.
20+
<2> Add or replace URI components.
21+
<3> Build `UriComponents`.
22+
<4> Expand URI variables, encode, and obtain the `URI`.
23+
24+
The above can be done as a single chain, and with a shortcut:
25+
26+
[source,java,indent=0]
27+
[subs="verbatim,quotes"]
28+
----
29+
String uriTemplate = "http://example.com/hotels/{hotel}";
30+
31+
URI uri = UriComponentsBuilder.fromUriString(uriTemplate)
32+
.queryParam("q", "{q}")
33+
.buildAndExpand("Westin", "123")
34+
.encode()
35+
.toUri();
36+
----
37+
38+
39+
40+
41+
[[web-uribuilder]]
42+
= UriBuilder
43+
44+
<<web-uricomponents,UriComponentsBuilder>> is an implementation of `UriBuilder`. Together
45+
`UriBuilderFactory` and `UriBuilder` provide a pluggable mechanism for building a URI
46+
from a URI template, as well as a way to share common properties such as a base URI,
47+
encoding strategy, and others.
48+
49+
Both the `RestTemplate` and the `WebClient` can be configured with a `UriBuilderFactory`,
50+
in order to customize how URIs are created from URI templates. The default implementation
51+
relies on `UriComponentsBuilder` internally and provides options to a common base URI,
52+
an alternative encoding mode strategy, and more.
53+
54+
An example of configuring the `RestTemplate`:
55+
56+
[source,java,indent=0]
57+
[subs="verbatim,quotes"]
58+
----
59+
String baseUrl = "http://example.com";
60+
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
61+
62+
RestTemplate restTemplate = new RestTemplate();
63+
restTemplate.setUriTemplateHandler(factory);
64+
----
65+
66+
Examples of configuring the `WebClient`:
67+
68+
[source,java,indent=0]
69+
[subs="verbatim,quotes"]
70+
----
71+
String baseUrl = "http://example.com";
72+
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
73+
74+
// Configure the UriBuilderFactory..
75+
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
76+
77+
// Or use shortcut on builder..
78+
WebClient client = WebClient.builder().baseUrl(baseUrl).build();
79+
80+
// Or use create shortcut...
81+
WebClient client = WebClient.create(baseUrl);
82+
----
83+
84+
You can also use `DefaultUriBuilderFactory` directly, as you would `UriComponentsBuilder`.
85+
The main difference is that `DefaultUriBuilderFactory` is stateful and can be re-used to
86+
prepare many URLs, sharing common configuration, such as a base URL, while
87+
`UriComponentsBuilder` is stateless and per URI.
88+
89+
An example of using the `DefaultUriBuilderFactory`:
90+
91+
[source,java,indent=0]
92+
[subs="verbatim,quotes"]
93+
----
94+
String baseUrl = "http://example.com";
95+
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);
96+
97+
URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
98+
.queryParam("q", "{q}")
99+
.build("Westin", "123"); // encoding strategy applied..
100+
----
101+
102+
103+
104+
105+
[[web-uri-encoding]]
106+
= URI Encoding
107+
108+
The default way of encoding URIs in `UriComponents` works as follows:
109+
110+
. URI variables are expanded.
111+
. URI components are encoded individually.
112+
113+
For each URI component, percent encoding is applied to all illegal characters, which
114+
includes non-US-ASCII characters, and other characters that are illegal within a given
115+
URI component type, as defined in RFC 3986.
116+
117+
[TIP]
118+
====
119+
The encoding in `UriComponents` is comparable to the multi-argument constructor of
120+
`java.net.URI`, as described in the "Escaped octets, quotation, encoding, and decoding"
121+
section of its class-level Javadoc.
122+
====
123+
124+
This default way of encoding *does not* encode all characters with reserved meaning, but
125+
only the ones that are illegal within a given URI component. If this is not what you
126+
expect you can use an alternative.
127+
128+
When using <<web-uribuilder,DefaultUriBuilderFactory>> you can switch to an alternative
129+
encoding strategy:
130+
131+
[source,java,indent=0]
132+
[subs="verbatim,quotes"]
133+
----
134+
String baseUrl = "http://example.com";
135+
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
136+
factory.setEncodingMode(EncodingMode.VALUES_ONLY);
137+
138+
// ...
139+
----
140+
141+
The above encoding strategy applies `UriUtils.encode(String, Charset)` to each URI
142+
variable value prior to expanding it. Effectively it encodes all characters with reserved
143+
meaning, therefore ensuring that expanded URI variable do not have any impact on the
144+
structure or meaning of the URI.
145+

src/docs/asciidoc/web/webmvc.adoc

Lines changed: 30 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2881,93 +2881,70 @@ Javadoc for more details.
28812881
[[mvc-uri-building]]
28822882
== URI Links
28832883

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

2887-
For example you can expand and encode a URI template string:
28882886

2889-
[source,java,indent=0]
2890-
[subs="verbatim,quotes"]
2891-
----
2892-
UriComponents uriComponents = UriComponentsBuilder.fromUriString(
2893-
"http://example.com/hotels/{hotel}/bookings/{booking}").build();
28942887

2895-
URI uri = uriComponents.expand("42", "21").encode().toUri();
2896-
----
2888+
include::web-uris.adoc[leveloffset=+2]
28972889

2898-
Note that `UriComponents` is immutable and the `expand()` and `encode()` operations
2899-
return new instances if necessary.
29002890

2901-
You can also expand and encode using individual URI components:
29022891

2903-
[source,java,indent=0]
2904-
[subs="verbatim,quotes"]
2905-
----
2906-
UriComponents uriComponents = UriComponentsBuilder.newInstance()
2907-
.scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build()
2908-
.expand("42", "21")
2909-
.encode();
2910-
----
2892+
[[mvc-servleturicomponentsbuilder]]
2893+
=== Servlet request relative
29112894

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

29152897
[source,java,indent=0]
29162898
[subs="verbatim,quotes"]
29172899
----
29182900
HttpServletRequest request = ...
29192901
2920-
// Re-use host, scheme, port, path and query string
2921-
// Replace the "accountId" query param
2902+
// Re-uses host, scheme, port, path and query string...
29222903
29232904
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request)
29242905
.replaceQueryParam("accountId", "{id}").build()
29252906
.expand("123")
29262907
.encode();
29272908
----
29282909

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

29322912
[source,java,indent=0]
29332913
[subs="verbatim,quotes"]
29342914
----
2935-
// Re-use host, port and context path
2936-
// Append "/accounts" to the path
2915+
// Re-uses host, port and context path...
29372916
29382917
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request)
29392918
.path("/accounts").build()
29402919
----
29412920

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

29452923
[source,java,indent=0]
29462924
[subs="verbatim,quotes"]
29472925
----
2948-
// Re-use host, port, context path
2949-
// Append the literal part of the servlet mapping to the path
2950-
// Append "/accounts" to the path
2926+
// Re-uses host, port, context path, and Servlet prefix...
29512927
29522928
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request)
29532929
.path("/accounts").build()
29542930
----
29552931

2956-
[TIP]
2932+
[CAUTION]
29572933
====
2958-
Both `ServletUriComponentsBuilder` and `MvcUriComponentsBuilder` detect, extract, and use
2959-
information from the "Forwarded" header, or from "X-Forwarded-Host", "X-Forwarded-Port",
2960-
and "X-Forwarded-Proto" if "Forwarded" is not present, so that the resulting links reflect
2961-
the original request. Note that you can also use the
2962-
<<filters-forwarded-headers,ForwardedHeaderFilter>> to the same once, globally.
2934+
`ServletUriComponentsBuilder` detects and uses information from the "Forwarded",
2935+
"X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" headers, so the resulting
2936+
links reflect the original request. You need to ensure that your application is behind
2937+
a trusted proxy which filters out such headers coming from outside. Also consider using
2938+
the <<filters-forwarded-headers,ForwardedHeaderFilter>> which processes such headers once
2939+
per request, and also provides an option to remove and ignore such headers.
29632940
====
29642941

29652942

29662943

29672944
[[mvc-links-to-controllers]]
2968-
=== Links to Controllers
2945+
=== Links to controllers
29692946

2970-
Spring MVC also provides a mechanism for building links to controller methods. For example, given:
2947+
Spring MVC provides a mechanism to prepare links to controller methods. For example:
29712948

29722949
[source,java,indent=0]
29732950
[subs="verbatim,quotes"]
@@ -3036,6 +3013,17 @@ with a base URL and then use the instance-based "withXxx" methods. For example:
30363013
URI uri = uriComponents.encode().toUri();
30373014
----
30383015

3016+
[CAUTION]
3017+
====
3018+
`MvcUriComponentsBuilder` detects and uses information from the "Forwarded",
3019+
"X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" headers, so the resulting
3020+
links reflect the original request. You need to ensure that your application is behind
3021+
a trusted proxy which filters out such headers coming from outside. Also consider using
3022+
the <<filters-forwarded-headers,ForwardedHeaderFilter>> which processes such headers once
3023+
per request, and also provides an option to remove and ignore such headers.
3024+
====
3025+
3026+
30393027

30403028

30413029
[[mvc-links-to-controllers-from-views]]

0 commit comments

Comments
 (0)