From 2b94205ba94f4e901efd7dece82da543d71c0573 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 19 Jul 2019 10:43:50 +0100 Subject: [PATCH 1/3] Update docs on multipart with RestTemplate Replace docs on using MultipartBodyBuilder for the RestTemplate with examples that show MultiValueMap. Originally the idea was to make MultipartBodyBuilder accessible to the RestTemplate too, but with support for async parts that's no longer a good fit. Closes gh-23295 --- .../web/client/RestOperations.java | 29 +++++-------- src/docs/asciidoc/integration.adoc | 41 ++++++++++--------- 2 files changed, 31 insertions(+), 39 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/client/RestOperations.java b/spring-web/src/main/java/org/springframework/web/client/RestOperations.java index 33b1d01e1f6..03d3cfa3505 100644 --- a/spring-web/src/main/java/org/springframework/web/client/RestOperations.java +++ b/spring-web/src/main/java/org/springframework/web/client/RestOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -153,8 +153,7 @@ public interface RestOperations { * {@link org.springframework.util.MultiValueMap MultiValueMap} to create a multipart request. * The values in the {@code MultiValueMap} can be any Object representing the body of the part, * or an {@link org.springframework.http.HttpEntity HttpEntity} representing a part with body - * and headers. The {@code MultiValueMap} can be built conveniently using - * {@link org.springframework.http.client.MultipartBodyBuilder MultipartBodyBuilder}. + * and headers. * @param url the URL * @param request the Object to be POSTed (may be {@code null}) * @param uriVariables the variables to expand the template @@ -174,8 +173,7 @@ public interface RestOperations { * {@link org.springframework.util.MultiValueMap MultiValueMap} to create a multipart request. * The values in the {@code MultiValueMap} can be any Object representing the body of the part, * or an {@link org.springframework.http.HttpEntity HttpEntity} representing a part with body - * and headers. The {@code MultiValueMap} can be built conveniently using - * {@link org.springframework.http.client.MultipartBodyBuilder MultipartBodyBuilder}. + * and headers. * @param url the URL * @param request the Object to be POSTed (may be {@code null}) * @param uriVariables the variables to expand the template @@ -195,8 +193,7 @@ public interface RestOperations { * {@link org.springframework.util.MultiValueMap MultiValueMap} to create a multipart request. * The values in the {@code MultiValueMap} can be any Object representing the body of the part, * or an {@link org.springframework.http.HttpEntity HttpEntity} representing a part with body - * and headers. The {@code MultiValueMap} can be built conveniently using - * {@link org.springframework.http.client.MultipartBodyBuilder MultipartBodyBuilder}. + * and headers. * @param url the URL * @param request the Object to be POSTed (may be {@code null}) * @return the value for the {@code Location} header @@ -215,8 +212,7 @@ public interface RestOperations { * {@link org.springframework.util.MultiValueMap MultiValueMap} to create a multipart request. * The values in the {@code MultiValueMap} can be any Object representing the body of the part, * or an {@link org.springframework.http.HttpEntity HttpEntity} representing a part with body - * and headers. The {@code MultiValueMap} can be built conveniently using - * {@link org.springframework.http.client.MultipartBodyBuilder MultipartBodyBuilder}. + * and headers. * @param url the URL * @param request the Object to be POSTed (may be {@code null}) * @param responseType the type of the return value @@ -238,8 +234,7 @@ public interface RestOperations { * {@link org.springframework.util.MultiValueMap MultiValueMap} to create a multipart request. * The values in the {@code MultiValueMap} can be any Object representing the body of the part, * or an {@link org.springframework.http.HttpEntity HttpEntity} representing a part with body - * and headers. The {@code MultiValueMap} can be built conveniently using - * {@link org.springframework.http.client.MultipartBodyBuilder MultipartBodyBuilder}. + * and headers. * @param url the URL * @param request the Object to be POSTed (may be {@code null}) * @param responseType the type of the return value @@ -260,8 +255,7 @@ public interface RestOperations { * {@link org.springframework.util.MultiValueMap MultiValueMap} to create a multipart request. * The values in the {@code MultiValueMap} can be any Object representing the body of the part, * or an {@link org.springframework.http.HttpEntity HttpEntity} representing a part with body - * and headers. The {@code MultiValueMap} can be built conveniently using - * {@link org.springframework.http.client.MultipartBodyBuilder MultipartBodyBuilder}. + * and headers. * @param url the URL * @param request the Object to be POSTed (may be {@code null}) * @param responseType the type of the return value @@ -281,8 +275,7 @@ public interface RestOperations { * {@link org.springframework.util.MultiValueMap MultiValueMap} to create a multipart request. * The values in the {@code MultiValueMap} can be any Object representing the body of the part, * or an {@link org.springframework.http.HttpEntity HttpEntity} representing a part with body - * and headers. The {@code MultiValueMap} can be built conveniently using - * {@link org.springframework.http.client.MultipartBodyBuilder MultipartBodyBuilder}. + * and headers. * @param url the URL * @param request the Object to be POSTed (may be {@code null}) * @param uriVariables the variables to expand the template @@ -303,8 +296,7 @@ public interface RestOperations { * {@link org.springframework.util.MultiValueMap MultiValueMap} to create a multipart request. * The values in the {@code MultiValueMap} can be any Object representing the body of the part, * or an {@link org.springframework.http.HttpEntity HttpEntity} representing a part with body - * and headers. The {@code MultiValueMap} can be built conveniently using - * {@link org.springframework.http.client.MultipartBodyBuilder MultipartBodyBuilder}. + * and headers. * @param url the URL * @param request the Object to be POSTed (may be {@code null}) * @param uriVariables the variables to expand the template @@ -324,8 +316,7 @@ public interface RestOperations { * {@link org.springframework.util.MultiValueMap MultiValueMap} to create a multipart request. * The values in the {@code MultiValueMap} can be any Object representing the body of the part, * or an {@link org.springframework.http.HttpEntity HttpEntity} representing a part with body - * and headers. The {@code MultiValueMap} can be built conveniently using - * {@link org.springframework.http.client.MultipartBodyBuilder MultipartBodyBuilder}. + * and headers. * @param url the URL * @param request the Object to be POSTed (may be {@code null}) * @return the converted object diff --git a/src/docs/asciidoc/integration.adoc b/src/docs/asciidoc/integration.adoc index 3e57e04f06f..66700f54858 100644 --- a/src/docs/asciidoc/integration.adoc +++ b/src/docs/asciidoc/integration.adoc @@ -1277,45 +1277,46 @@ to serialize only a subset of the object properties, as the following example sh [[rest-template-multipart]] ===== Multipart -To send multipart data, you need to provide a `MultiValueMap` whose values are -either `Object` instances that represent part content or `HttpEntity` instances that represent the content and -headers for a part. `MultipartBodyBuilder` provides a convenient API to prepare a -multipart request, as the following example shows: +To send multipart data, you need to provide a `MultiValueMap` whose values +may be an `Object` for part content, a `Resource` for a file part, or an `HttpEntity` for +part content with headers. For example: ==== [source,java,intent=0] [subs="verbatim,quotes"] ---- - MultipartBodyBuilder builder = new MultipartBodyBuilder(); - builder.part("fieldPart", "fieldValue"); - builder.part("filePart", new FileSystemResource("...logo.png")); - builder.part("jsonPart", new Person("Jason")); + MultiValueMap parts = new LinkedMultiValueMap<>(); - MultiValueMap> parts = builder.build(); + parts.add("fieldPart", "fieldValue"); + parts.add("filePart", new FileSystemResource("...logo.png")); + parts.add("jsonPart", new Person("Jason")); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_XML); + parts.add("xmlPart", new HttpEntity<>(myBean, headers)); ---- ==== In most cases, you do not have to specify the `Content-Type` for each part. The content -type is determined automatically based on the `HttpMessageConverter` chosen to serialize it -or, in the case of a `Resource`, based on the file extension. If necessary, you can -explicitly provide the `MediaType` to use for each part through one of the overloaded -builder `part` methods. +type is determined automatically based on the `HttpMessageConverter` chosen to serialize +it or, in the case of a `Resource` based on the file extension. If necessary, you can +explicitly provide the `MediaType` with an `HttpEntity` wrapper. -Once the `MultiValueMap` is ready, you can pass it to the `RestTemplate`, as the following example shows: +Once the `MultiValueMap` is ready, you can pass it to the `RestTemplate`, as show below: ==== [source,java,intent=0] [subs="verbatim,quotes"] ---- - MultipartBodyBuilder builder = ...; - template.postForObject("https://example.com/upload", builder.build(), Void.class); + MultiValueMap parts = ...; + template.postForObject("https://example.com/upload", parts, Void.class); ---- ==== -If the `MultiValueMap` contains at least one non-`String` value, which could also be -represent regular form data (that is, `application/x-www-form-urlencoded`), you need not -set the `Content-Type` to `multipart/form-data`. This is always the case when you use -`MultipartBodyBuilder` which ensures an `HttpEntity` wrapper. +If the `MultiValueMap` contains at least one non-`String` value, the `Content-Type` is set +to `multipart/form-data` by the `FormHttpMessageConverter`. If the `MultiValueMap` has +`String` values the `Content-Type` is defaulted to `application/x-www-form-urlencoded`. +If necessary the `Content-Type` may also be set explicitly. [[rest-async-resttemplate]] From 4973e110ee99759d4e27c2f904d84c5cbabcbe78 Mon Sep 17 00:00:00 2001 From: Andreas Kluth Date: Wed, 17 Jul 2019 15:41:25 +0200 Subject: [PATCH 2/3] An empty X-Forwarded-Prefix with a path containing escape sequences leads to exceptions. --- .../adapter/ForwardedHeaderTransformer.java | 2 +- .../adapter/ForwardedHeaderTransformerTests.java | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java b/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java index 149f22fe5b2..3695d53619b 100644 --- a/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java +++ b/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java @@ -96,7 +96,7 @@ public class ForwardedHeaderTransformer implements Function Date: Fri, 19 Jul 2019 09:39:42 +0100 Subject: [PATCH 3/3] Polish --- .../server/adapter/ForwardedHeaderTransformer.java | 2 +- .../adapter/ForwardedHeaderTransformerTests.java | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java b/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java index 3695d53619b..b3990166cb1 100644 --- a/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java +++ b/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. diff --git a/spring-web/src/test/java/org/springframework/web/server/adapter/ForwardedHeaderTransformerTests.java b/spring-web/src/test/java/org/springframework/web/server/adapter/ForwardedHeaderTransformerTests.java index dc11e5b0cbb..585cfb73d0c 100644 --- a/spring-web/src/test/java/org/springframework/web/server/adapter/ForwardedHeaderTransformerTests.java +++ b/spring-web/src/test/java/org/springframework/web/server/adapter/ForwardedHeaderTransformerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -90,10 +90,10 @@ public class ForwardedHeaderTransformerTests { assertForwardedHeadersRemoved(request); } - @Test - public void emptyXForwardedPrefixShouldNotLeadToDecodedPath() throws Exception { + @Test // gh-23305 + public void xForwardedPrefixShouldNotLeadToDecodedPath() throws Exception { HttpHeaders headers = new HttpHeaders(); - headers.add("X-Forwarded-Prefix", ""); + headers.add("X-Forwarded-Prefix", "/prefix"); ServerHttpRequest request = MockServerHttpRequest .method(HttpMethod.GET, new URI("https://example.com/a%20b?q=a%2Bb")) .headers(headers) @@ -101,8 +101,8 @@ public class ForwardedHeaderTransformerTests { request = this.requestMutator.apply(request); - assertThat(request.getURI()).isEqualTo(new URI("https://example.com/a%20b?q=a%2Bb")); - assertThat(request.getPath().value()).isEqualTo("/a%20b"); + assertEquals(new URI("https://example.com/prefix/a%20b?q=a%2Bb"), request.getURI()); + assertEquals("/prefix/a%20b", request.getPath().value()); assertForwardedHeadersRemoved(request); }