View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.web.client;
18  
19  import java.net.URI;
20  import java.nio.charset.Charset;
21  import java.util.EnumSet;
22  import java.util.Set;
23  import java.util.concurrent.Future;
24  
25  import org.junit.Before;
26  import org.junit.Test;
27  
28  import org.springframework.core.io.ClassPathResource;
29  import org.springframework.core.io.Resource;
30  import org.springframework.http.HttpEntity;
31  import org.springframework.http.HttpHeaders;
32  import org.springframework.http.HttpMethod;
33  import org.springframework.http.HttpStatus;
34  import org.springframework.http.MediaType;
35  import org.springframework.http.ResponseEntity;
36  import org.springframework.http.client.HttpComponentsAsyncClientHttpRequestFactory;
37  import org.springframework.util.LinkedMultiValueMap;
38  import org.springframework.util.MultiValueMap;
39  import org.springframework.util.concurrent.ListenableFuture;
40  import org.springframework.util.concurrent.ListenableFutureCallback;
41  
42  import static org.junit.Assert.*;
43  
44  /**
45   * @author Arjen Poutsma
46   * @author Sebastien Deleuze
47   */
48  public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCase {
49  
50  	private AsyncRestTemplate template;
51  
52  
53  	@Before
54  	public void createTemplate() {
55  		template = new AsyncRestTemplate(new HttpComponentsAsyncClientHttpRequestFactory());
56  	}
57  
58  
59  	@Test
60  	public void getEntity() throws Exception {
61  		Future<ResponseEntity<String>> futureEntity =
62  				template.getForEntity(baseUrl + "/{method}", String.class, "get");
63  		ResponseEntity<String> entity = futureEntity.get();
64  		assertEquals("Invalid content", helloWorld, entity.getBody());
65  		assertFalse("No headers", entity.getHeaders().isEmpty());
66  		assertEquals("Invalid content-type", textContentType, entity.getHeaders().getContentType());
67  		assertEquals("Invalid status code", HttpStatus.OK, entity.getStatusCode());
68  	}
69  
70  	@Test
71  	public void multipleFutureGets() throws Exception {
72  		Future<ResponseEntity<String>> futureEntity =
73  				template.getForEntity(baseUrl + "/{method}", String.class, "get");
74  		futureEntity.get();
75  		futureEntity.get();
76  	}
77  
78  	@Test
79  	public void getEntityCallback() throws Exception {
80  		ListenableFuture<ResponseEntity<String>> futureEntity =
81  				template.getForEntity(baseUrl + "/{method}", String.class, "get");
82  		futureEntity.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
83  			@Override
84  			public void onSuccess(ResponseEntity<String> entity) {
85  				assertEquals("Invalid content", helloWorld, entity.getBody());
86  				assertFalse("No headers", entity.getHeaders().isEmpty());
87  				assertEquals("Invalid content-type", textContentType, entity.getHeaders().getContentType());
88  				assertEquals("Invalid status code", HttpStatus.OK, entity.getStatusCode());
89  			}
90  			@Override
91  			public void onFailure(Throwable ex) {
92  				fail(ex.getMessage());
93  			}
94  		});
95  		// wait till done
96  		while (!futureEntity.isDone()) {
97  		}
98  	}
99  
100 	@Test
101 	public void getEntityCallbackWithLambdas() throws Exception {
102 		ListenableFuture<ResponseEntity<String>> futureEntity =
103 				template.getForEntity(baseUrl + "/{method}", String.class, "get");
104 		futureEntity.addCallback((entity) -> {
105 			assertEquals("Invalid content", helloWorld, entity.getBody());
106 			assertFalse("No headers", entity.getHeaders().isEmpty());
107 			assertEquals("Invalid content-type", textContentType, entity.getHeaders().getContentType());
108 			assertEquals("Invalid status code", HttpStatus.OK, entity.getStatusCode());
109 		}, ex -> fail(ex.getMessage()));
110 		// wait till done
111 		while (!futureEntity.isDone()) {
112 		}
113 	}
114 
115 	@Test
116 	public void getNoResponse() throws Exception {
117 		Future<ResponseEntity<String>> futureEntity = template.getForEntity(baseUrl + "/get/nothing", String.class);
118 		ResponseEntity<String> entity = futureEntity.get();
119 		assertNull("Invalid content", entity.getBody());
120 	}
121 
122 
123 	@Test
124 	public void getNoContentTypeHeader() throws Exception {
125 		Future<ResponseEntity<byte[]>> futureEntity = template.getForEntity(baseUrl + "/get/nocontenttype", byte[].class);
126 		ResponseEntity<byte[]> responseEntity = futureEntity.get();
127 		assertArrayEquals("Invalid content", helloWorld.getBytes("UTF-8"), responseEntity.getBody());
128 	}
129 
130 
131 	@Test
132 	public void getNoContent() throws Exception {
133 		Future<ResponseEntity<String>> responseFuture = template.getForEntity(baseUrl + "/status/nocontent", String.class);
134 		ResponseEntity<String> entity = responseFuture.get();
135 		assertEquals("Invalid response code", HttpStatus.NO_CONTENT, entity.getStatusCode());
136 		assertNull("Invalid content", entity.getBody());
137 	}
138 
139 	@Test
140 	public void getNotModified() throws Exception {
141 		Future<ResponseEntity<String>> responseFuture = template.getForEntity(baseUrl + "/status/notmodified", String.class);
142 		ResponseEntity<String> entity = responseFuture.get();
143 		assertEquals("Invalid response code", HttpStatus.NOT_MODIFIED, entity.getStatusCode());
144 		assertNull("Invalid content", entity.getBody());
145 	}
146 
147 	@Test
148 	public void headForHeaders() throws Exception {
149 		Future<HttpHeaders> headersFuture = template.headForHeaders(baseUrl + "/get");
150 		HttpHeaders headers = headersFuture.get();
151 		assertTrue("No Content-Type header", headers.containsKey("Content-Type"));
152 	}
153 
154 	@Test
155 	public void headForHeadersCallback() throws Exception {
156 		ListenableFuture<HttpHeaders> headersFuture = template.headForHeaders(baseUrl + "/get");
157 		headersFuture.addCallback(new ListenableFutureCallback<HttpHeaders>() {
158 			@Override
159 			public void onSuccess(HttpHeaders result) {
160 				assertTrue("No Content-Type header", result.containsKey("Content-Type"));
161 			}
162 			@Override
163 			public void onFailure(Throwable ex) {
164 				fail(ex.getMessage());
165 			}
166 		});
167 		while (!headersFuture.isDone()) {
168 		}
169 	}
170 
171 	@Test
172 	public void headForHeadersCallbackWithLambdas() throws Exception {
173 		ListenableFuture<HttpHeaders> headersFuture = template.headForHeaders(baseUrl + "/get");
174 		headersFuture.addCallback(result -> assertTrue("No Content-Type header",
175 				result.containsKey("Content-Type")), ex -> fail(ex.getMessage()));
176 		while (!headersFuture.isDone()) {
177 		}
178 	}
179 
180 	@Test
181 	public void postForLocation() throws Exception  {
182 		HttpHeaders entityHeaders = new HttpHeaders();
183 		entityHeaders.setContentType(new MediaType("text", "plain", Charset.forName("ISO-8859-15")));
184 		HttpEntity<String> entity = new HttpEntity<String>(helloWorld, entityHeaders);
185 		Future<URI> locationFuture = template.postForLocation(baseUrl + "/{method}", entity, "post");
186 		URI location = locationFuture.get();
187 		assertEquals("Invalid location", new URI(baseUrl + "/post/1"), location);
188 	}
189 
190 	@Test
191 	public void postForLocationCallback() throws Exception  {
192 		HttpHeaders entityHeaders = new HttpHeaders();
193 		entityHeaders.setContentType(new MediaType("text", "plain", Charset.forName("ISO-8859-15")));
194 		HttpEntity<String> entity = new HttpEntity<String>(helloWorld, entityHeaders);
195 		final URI expected = new URI(baseUrl + "/post/1");
196 		ListenableFuture<URI> locationFuture = template.postForLocation(baseUrl + "/{method}", entity, "post");
197 		locationFuture.addCallback(new ListenableFutureCallback<URI>() {
198 			@Override
199 			public void onSuccess(URI result) {
200 				assertEquals("Invalid location", expected, result);
201 			}
202 			@Override
203 			public void onFailure(Throwable ex) {
204 				fail(ex.getMessage());
205 			}
206 		});
207 		while (!locationFuture.isDone()) {
208 		}
209 	}
210 
211 	@Test
212 	public void postForLocationCallbackWithLambdas() throws Exception  {
213 		HttpHeaders entityHeaders = new HttpHeaders();
214 		entityHeaders.setContentType(new MediaType("text", "plain", Charset.forName("ISO-8859-15")));
215 		HttpEntity<String> entity = new HttpEntity<String>(helloWorld, entityHeaders);
216 		final URI expected = new URI(baseUrl + "/post/1");
217 		ListenableFuture<URI> locationFuture = template.postForLocation(baseUrl + "/{method}", entity, "post");
218 		locationFuture.addCallback(result -> assertEquals("Invalid location", expected, result),
219 				ex -> fail(ex.getMessage()));
220 		while (!locationFuture.isDone()) {
221 		}
222 	}
223 
224 	@Test
225 	public void postForEntity() throws Exception  {
226 		HttpEntity<String> requestEntity = new HttpEntity<>(helloWorld);
227 		Future<ResponseEntity<String>> responseEntityFuture =
228 				template.postForEntity(baseUrl + "/{method}", requestEntity, String.class, "post");
229 		ResponseEntity<String> responseEntity = responseEntityFuture.get();
230 		assertEquals("Invalid content", helloWorld, responseEntity.getBody());
231 	}
232 
233 	@Test
234 	public void postForEntityCallback() throws Exception  {
235 		HttpEntity<String> requestEntity = new HttpEntity<>(helloWorld);
236 		ListenableFuture<ResponseEntity<String>> responseEntityFuture =
237 				template.postForEntity(baseUrl + "/{method}", requestEntity, String.class, "post");
238 		responseEntityFuture.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
239 			@Override
240 			public void onSuccess(ResponseEntity<String> result) {
241 				assertEquals("Invalid content", helloWorld, result.getBody());
242 			}
243 			@Override
244 			public void onFailure(Throwable ex) {
245 				fail(ex.getMessage());
246 			}
247 		});
248 		while (!responseEntityFuture.isDone()) {
249 		}
250 	}
251 
252 	@Test
253 	public void postForEntityCallbackWithLambdas() throws Exception  {
254 		HttpEntity<String> requestEntity = new HttpEntity<>(helloWorld);
255 		ListenableFuture<ResponseEntity<String>> responseEntityFuture =
256 				template.postForEntity(baseUrl + "/{method}", requestEntity, String.class, "post");
257 		responseEntityFuture.addCallback(result -> assertEquals("Invalid content", helloWorld, result.getBody()),
258 				ex -> fail(ex.getMessage()));
259 		while (!responseEntityFuture.isDone()) {
260 		}
261 	}
262 
263 	@Test
264 	public void put() throws Exception  {
265 		HttpEntity<String> requestEntity = new HttpEntity<>(helloWorld);
266 		Future<?> responseEntityFuture = template.put(baseUrl + "/{method}", requestEntity, "put");
267 		responseEntityFuture.get();
268 	}
269 
270 	@Test
271 	public void putCallback() throws Exception  {
272 		HttpEntity<String> requestEntity = new HttpEntity<>(helloWorld);
273 		ListenableFuture<?> responseEntityFuture = template.put(baseUrl + "/{method}", requestEntity, "put");
274 		responseEntityFuture.addCallback(new ListenableFutureCallback<Object>() {
275 			@Override
276 			public void onSuccess(Object result) {
277 				assertNull(result);
278 			}
279 			@Override
280 			public void onFailure(Throwable ex) {
281 				fail(ex.getMessage());
282 			}
283 		});
284 		while (!responseEntityFuture.isDone()) {
285 		}
286 	}
287 
288 	@Test
289 	public void delete() throws Exception  {
290 		Future<?> deletedFuture = template.delete(new URI(baseUrl + "/delete"));
291 		deletedFuture.get();
292 	}
293 
294 	@Test
295 	public void deleteCallback() throws Exception  {
296 		ListenableFuture<?> deletedFuture = template.delete(new URI(baseUrl + "/delete"));
297 		deletedFuture.addCallback(new ListenableFutureCallback<Object>() {
298 			@Override
299 			public void onSuccess(Object result) {
300 				assertNull(result);
301 			}
302 			@Override
303 			public void onFailure(Throwable ex) {
304 				fail(ex.getMessage());
305 			}
306 		});
307 		while (!deletedFuture.isDone()) {
308 		}
309 	}
310 
311 	@Test
312 	public void deleteCallbackWithLambdas() throws Exception  {
313 		ListenableFuture<?> deletedFuture = template.delete(new URI(baseUrl + "/delete"));
314 		deletedFuture.addCallback(result -> assertNull(result), ex -> fail(ex.getMessage()));
315 		while (!deletedFuture.isDone()) {
316 		}
317 	}
318 
319 	@Test
320 	public void notFound() throws Exception {
321 		try {
322 			Future<?> future = template.execute(baseUrl + "/status/notfound", HttpMethod.GET, null, null);
323 			future.get();
324 			fail("HttpClientErrorException expected");
325 		}
326 		catch (HttpClientErrorException ex) {
327 			assertEquals(HttpStatus.NOT_FOUND, ex.getStatusCode());
328 			assertNotNull(ex.getStatusText());
329 			assertNotNull(ex.getResponseBodyAsString());
330 		}
331 	}
332 
333 	@Test
334 	public void notFoundCallback() throws Exception {
335 		ListenableFuture<?> future = template.execute(baseUrl + "/status/notfound", HttpMethod.GET, null, null);
336 		future.addCallback(new ListenableFutureCallback<Object>() {
337 			@Override
338 			public void onSuccess(Object result) {
339 				fail("onSuccess not expected");
340 			}
341 			@Override
342 			public void onFailure(Throwable t) {
343 				assertTrue(t instanceof HttpClientErrorException);
344 				HttpClientErrorException ex = (HttpClientErrorException) t;
345 				assertEquals(HttpStatus.NOT_FOUND, ex.getStatusCode());
346 				assertNotNull(ex.getStatusText());
347 				assertNotNull(ex.getResponseBodyAsString());
348 			}
349 		});
350 		while (!future.isDone()) {
351 		}
352 	}
353 
354 	@Test
355 	public void notFoundCallbackWithLambdas() throws Exception {
356 		ListenableFuture<?> future = template.execute(baseUrl + "/status/notfound", HttpMethod.GET, null, null);
357 		future.addCallback(result -> fail("onSuccess not expected"), ex -> {
358 				assertTrue(ex instanceof HttpClientErrorException);
359 				HttpClientErrorException hcex = (HttpClientErrorException) ex;
360 				assertEquals(HttpStatus.NOT_FOUND, hcex.getStatusCode());
361 				assertNotNull(hcex.getStatusText());
362 				assertNotNull(hcex.getResponseBodyAsString());
363 		});
364 		while (!future.isDone()) {
365 		}
366 	}
367 
368 	@Test
369 	public void serverError() throws Exception {
370 		try {
371 			Future<Void> future = template.execute(baseUrl + "/status/server", HttpMethod.GET, null, null);
372 			future.get();
373 			fail("HttpServerErrorException expected");
374 		}
375 		catch (HttpServerErrorException ex) {
376 			assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, ex.getStatusCode());
377 			assertNotNull(ex.getStatusText());
378 			assertNotNull(ex.getResponseBodyAsString());
379 		}
380 	}
381 
382 	@Test
383 	public void serverErrorCallback() throws Exception {
384 		ListenableFuture<Void> future = template.execute(baseUrl + "/status/server", HttpMethod.GET, null, null);
385 		future.addCallback(new ListenableFutureCallback<Void>() {
386 			@Override
387 			public void onSuccess(Void result) {
388 				fail("onSuccess not expected");
389 			}
390 			@Override
391 			public void onFailure(Throwable ex) {
392 				assertTrue(ex instanceof HttpServerErrorException);
393 				HttpServerErrorException hsex = (HttpServerErrorException) ex;
394 				assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, hsex.getStatusCode());
395 				assertNotNull(hsex.getStatusText());
396 				assertNotNull(hsex.getResponseBodyAsString());
397 			}
398 		});
399 		while (!future.isDone()) {
400 		}
401 	}
402 
403 	@Test
404 	public void serverErrorCallbackWithLambdas() throws Exception {
405 		ListenableFuture<Void> future = template.execute(baseUrl + "/status/server", HttpMethod.GET, null, null);
406 		future.addCallback(result -> fail("onSuccess not expected"), ex -> {
407 				assertTrue(ex instanceof HttpServerErrorException);
408 				HttpServerErrorException hsex = (HttpServerErrorException) ex;
409 				assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, hsex.getStatusCode());
410 				assertNotNull(hsex.getStatusText());
411 				assertNotNull(hsex.getResponseBodyAsString());
412 		});
413 		while (!future.isDone()) {
414 		}
415 	}
416 
417 	@Test
418 	public void optionsForAllow() throws Exception {
419 		Future<Set<HttpMethod>> allowedFuture = template.optionsForAllow(new URI(baseUrl + "/get"));
420 		Set<HttpMethod> allowed = allowedFuture.get();
421 		assertEquals("Invalid response",
422 				EnumSet.of(HttpMethod.GET, HttpMethod.OPTIONS, HttpMethod.HEAD, HttpMethod.TRACE), allowed);
423 	}
424 
425 	@Test
426 	public void optionsForAllowCallback() throws Exception {
427 		ListenableFuture<Set<HttpMethod>> allowedFuture = template.optionsForAllow(new URI(baseUrl + "/get"));
428 		allowedFuture.addCallback(new ListenableFutureCallback<Set<HttpMethod>>() {
429 			@Override
430 			public void onSuccess(Set<HttpMethod> result) {
431 				assertEquals("Invalid response", EnumSet.of(HttpMethod.GET, HttpMethod.OPTIONS,
432 						HttpMethod.HEAD, HttpMethod.TRACE), result);
433 			}
434 			@Override
435 			public void onFailure(Throwable ex) {
436 				fail(ex.getMessage());
437 			}
438 		});
439 		while (!allowedFuture.isDone()) {
440 		}
441 	}
442 
443 	@Test
444 	public void optionsForAllowCallbackWithLambdas() throws Exception{
445 		ListenableFuture<Set<HttpMethod>> allowedFuture = template.optionsForAllow(new URI(baseUrl + "/get"));
446 		allowedFuture.addCallback(result -> assertEquals("Invalid response",
447 				EnumSet.of(HttpMethod.GET, HttpMethod.OPTIONS, HttpMethod.HEAD,HttpMethod.TRACE), result),
448 				ex -> fail(ex.getMessage()));
449 		while (!allowedFuture.isDone()) {
450 		}
451 	}
452 
453 	@Test
454 	@SuppressWarnings({ "unchecked", "rawtypes" })
455 	public void exchangeGet() throws Exception {
456 		HttpHeaders requestHeaders = new HttpHeaders();
457 		requestHeaders.set("MyHeader", "MyValue");
458 		HttpEntity<?> requestEntity = new HttpEntity(requestHeaders);
459 		Future<ResponseEntity<String>> responseFuture =
460 				template.exchange(baseUrl + "/{method}", HttpMethod.GET, requestEntity, String.class, "get");
461 		ResponseEntity<String> response = responseFuture.get();
462 		assertEquals("Invalid content", helloWorld, response.getBody());
463 	}
464 
465 	@Test
466 	@SuppressWarnings({ "unchecked", "rawtypes" })
467 	public void exchangeGetCallback() throws Exception {
468 		HttpHeaders requestHeaders = new HttpHeaders();
469 		requestHeaders.set("MyHeader", "MyValue");
470 		HttpEntity<?> requestEntity = new HttpEntity(requestHeaders);
471 		ListenableFuture<ResponseEntity<String>> responseFuture =
472 				template.exchange(baseUrl + "/{method}", HttpMethod.GET, requestEntity, String.class, "get");
473 		responseFuture.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
474 			@Override
475 			public void onSuccess(ResponseEntity<String> result) {
476 				assertEquals("Invalid content", helloWorld, result.getBody());
477 			}
478 			@Override
479 			public void onFailure(Throwable ex) {
480 				fail(ex.getMessage());
481 			}
482 		});
483 		while (!responseFuture.isDone()) {
484 		}
485 	}
486 
487 	@Test
488 	@SuppressWarnings({ "unchecked", "rawtypes" })
489 	public void exchangeGetCallbackWithLambdas() throws Exception {
490 		HttpHeaders requestHeaders = new HttpHeaders();
491 		requestHeaders.set("MyHeader", "MyValue");
492 		HttpEntity<?> requestEntity = new HttpEntity(requestHeaders);
493 		ListenableFuture<ResponseEntity<String>> responseFuture =
494 				template.exchange(baseUrl + "/{method}", HttpMethod.GET, requestEntity, String.class, "get");
495 		responseFuture.addCallback(result -> assertEquals("Invalid content", helloWorld,
496 				result.getBody()), ex -> fail(ex.getMessage()));
497 		while (!responseFuture.isDone()) {
498 		}
499 	}
500 
501 	@Test
502 	public void exchangePost() throws Exception {
503 		HttpHeaders requestHeaders = new HttpHeaders();
504 		requestHeaders.set("MyHeader", "MyValue");
505 		requestHeaders.setContentType(MediaType.TEXT_PLAIN);
506 		HttpEntity<String> requestEntity = new HttpEntity<String>(helloWorld, requestHeaders);
507 		Future<ResponseEntity<Void>> resultFuture =
508 				template.exchange(baseUrl + "/{method}", HttpMethod.POST, requestEntity, Void.class, "post");
509 		ResponseEntity<Void> result = resultFuture.get();
510 		assertEquals("Invalid location", new URI(baseUrl + "/post/1"),
511 				result.getHeaders().getLocation());
512 		assertFalse(result.hasBody());
513 	}
514 
515 	@Test
516 	public void exchangePostCallback() throws Exception {
517 		HttpHeaders requestHeaders = new HttpHeaders();
518 		requestHeaders.set("MyHeader", "MyValue");
519 		requestHeaders.setContentType(MediaType.TEXT_PLAIN);
520 		HttpEntity<String> requestEntity = new HttpEntity<String>(helloWorld, requestHeaders);
521 		ListenableFuture<ResponseEntity<Void>> resultFuture =
522 				template.exchange(baseUrl + "/{method}", HttpMethod.POST, requestEntity, Void.class, "post");
523 		final URI expected =new URI(baseUrl + "/post/1");
524 		resultFuture.addCallback(new ListenableFutureCallback<ResponseEntity<Void>>() {
525 			@Override
526 			public void onSuccess(ResponseEntity<Void> result) {
527 				assertEquals("Invalid location", expected, result.getHeaders().getLocation());
528 				assertFalse(result.hasBody());
529 			}
530 			@Override
531 			public void onFailure(Throwable ex) {
532 				fail(ex.getMessage());
533 			}
534 		});
535 		while (!resultFuture.isDone()) {
536 		}
537 	}
538 
539 	@Test
540 	public void exchangePostCallbackWithLambdas() throws Exception {
541 		HttpHeaders requestHeaders = new HttpHeaders();
542 		requestHeaders.set("MyHeader", "MyValue");
543 		requestHeaders.setContentType(MediaType.TEXT_PLAIN);
544 		HttpEntity<String> requestEntity = new HttpEntity<String>(helloWorld, requestHeaders);
545 		ListenableFuture<ResponseEntity<Void>> resultFuture =
546 				template.exchange(baseUrl + "/{method}", HttpMethod.POST, requestEntity, Void.class, "post");
547 		final URI expected =new URI(baseUrl + "/post/1");
548 		resultFuture.addCallback(result -> {
549 			assertEquals("Invalid location", expected, result.getHeaders().getLocation());
550 			assertFalse(result.hasBody());
551 			}, ex -> fail(ex.getMessage()));
552 		while (!resultFuture.isDone()) {
553 		}
554 	}
555 
556 	@Test
557 	public void multipart() throws Exception {
558 		MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
559 		parts.add("name 1", "value 1");
560 		parts.add("name 2", "value 2+1");
561 		parts.add("name 2", "value 2+2");
562 		Resource logo = new ClassPathResource("/org/springframework/http/converter/logo.jpg");
563 		parts.add("logo", logo);
564 
565 		HttpEntity<MultiValueMap<String, Object>> requestBody = new HttpEntity<>(parts);
566 		Future<URI> future = template.postForLocation(baseUrl + "/multipart", requestBody);
567 		future.get();
568 	}
569 
570 }