001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.client; 018 019import java.io.IOException; 020import java.lang.reflect.Type; 021import java.net.URI; 022import java.util.ArrayList; 023import java.util.Collections; 024import java.util.List; 025import java.util.Map; 026import java.util.Set; 027import java.util.stream.Collectors; 028import java.util.stream.Stream; 029 030import org.springframework.core.ParameterizedTypeReference; 031import org.springframework.http.HttpEntity; 032import org.springframework.http.HttpHeaders; 033import org.springframework.http.HttpMethod; 034import org.springframework.http.HttpStatus; 035import org.springframework.http.MediaType; 036import org.springframework.http.RequestEntity; 037import org.springframework.http.ResponseEntity; 038import org.springframework.http.client.ClientHttpRequest; 039import org.springframework.http.client.ClientHttpRequestFactory; 040import org.springframework.http.client.ClientHttpResponse; 041import org.springframework.http.client.support.InterceptingHttpAccessor; 042import org.springframework.http.converter.ByteArrayHttpMessageConverter; 043import org.springframework.http.converter.GenericHttpMessageConverter; 044import org.springframework.http.converter.HttpMessageConverter; 045import org.springframework.http.converter.ResourceHttpMessageConverter; 046import org.springframework.http.converter.StringHttpMessageConverter; 047import org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter; 048import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter; 049import org.springframework.http.converter.feed.RssChannelHttpMessageConverter; 050import org.springframework.http.converter.json.GsonHttpMessageConverter; 051import org.springframework.http.converter.json.JsonbHttpMessageConverter; 052import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; 053import org.springframework.http.converter.smile.MappingJackson2SmileHttpMessageConverter; 054import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; 055import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter; 056import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; 057import org.springframework.http.converter.xml.SourceHttpMessageConverter; 058import org.springframework.lang.Nullable; 059import org.springframework.util.Assert; 060import org.springframework.util.ClassUtils; 061import org.springframework.web.util.DefaultUriBuilderFactory; 062import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode; 063import org.springframework.web.util.UriTemplateHandler; 064 065/** 066 * Synchronous client to perform HTTP requests, exposing a simple, template 067 * method API over underlying HTTP client libraries such as the JDK 068 * {@code HttpURLConnection}, Apache HttpComponents, and others. 069 * 070 * <p>The RestTemplate offers templates for common scenarios by HTTP method, in 071 * addition to the generalized {@code exchange} and {@code execute} methods that 072 * support of less frequent cases. 073 * 074 * <p><strong>NOTE:</strong> As of 5.0 this class is in maintenance mode, with 075 * only minor requests for changes and bugs to be accepted going forward. Please, 076 * consider using the {@code org.springframework.web.reactive.client.WebClient} 077 * which has a more modern API and supports sync, async, and streaming scenarios. 078 * 079 * @author Arjen Poutsma 080 * @author Brian Clozel 081 * @author Roy Clarkson 082 * @author Juergen Hoeller 083 * @author Sam Brannen 084 * @since 3.0 085 * @see HttpMessageConverter 086 * @see RequestCallback 087 * @see ResponseExtractor 088 * @see ResponseErrorHandler 089 */ 090public class RestTemplate extends InterceptingHttpAccessor implements RestOperations { 091 092 private static final boolean romePresent; 093 094 private static final boolean jaxb2Present; 095 096 private static final boolean jackson2Present; 097 098 private static final boolean jackson2XmlPresent; 099 100 private static final boolean jackson2SmilePresent; 101 102 private static final boolean jackson2CborPresent; 103 104 private static final boolean gsonPresent; 105 106 private static final boolean jsonbPresent; 107 108 static { 109 ClassLoader classLoader = RestTemplate.class.getClassLoader(); 110 romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader); 111 jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader); 112 jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && 113 ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); 114 jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader); 115 jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader); 116 jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader); 117 gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader); 118 jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader); 119 } 120 121 122 private final List<HttpMessageConverter<?>> messageConverters = new ArrayList<>(); 123 124 private ResponseErrorHandler errorHandler = new DefaultResponseErrorHandler(); 125 126 private UriTemplateHandler uriTemplateHandler; 127 128 private final ResponseExtractor<HttpHeaders> headersExtractor = new HeadersExtractor(); 129 130 131 /** 132 * Create a new instance of the {@link RestTemplate} using default settings. 133 * Default {@link HttpMessageConverter HttpMessageConverters} are initialized. 134 */ 135 public RestTemplate() { 136 this.messageConverters.add(new ByteArrayHttpMessageConverter()); 137 this.messageConverters.add(new StringHttpMessageConverter()); 138 this.messageConverters.add(new ResourceHttpMessageConverter(false)); 139 try { 140 this.messageConverters.add(new SourceHttpMessageConverter<>()); 141 } 142 catch (Error err) { 143 // Ignore when no TransformerFactory implementation is available 144 } 145 this.messageConverters.add(new AllEncompassingFormHttpMessageConverter()); 146 147 if (romePresent) { 148 this.messageConverters.add(new AtomFeedHttpMessageConverter()); 149 this.messageConverters.add(new RssChannelHttpMessageConverter()); 150 } 151 152 if (jackson2XmlPresent) { 153 this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter()); 154 } 155 else if (jaxb2Present) { 156 this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter()); 157 } 158 159 if (jackson2Present) { 160 this.messageConverters.add(new MappingJackson2HttpMessageConverter()); 161 } 162 else if (gsonPresent) { 163 this.messageConverters.add(new GsonHttpMessageConverter()); 164 } 165 else if (jsonbPresent) { 166 this.messageConverters.add(new JsonbHttpMessageConverter()); 167 } 168 169 if (jackson2SmilePresent) { 170 this.messageConverters.add(new MappingJackson2SmileHttpMessageConverter()); 171 } 172 if (jackson2CborPresent) { 173 this.messageConverters.add(new MappingJackson2CborHttpMessageConverter()); 174 } 175 176 this.uriTemplateHandler = initUriTemplateHandler(); 177 } 178 179 /** 180 * Create a new instance of the {@link RestTemplate} based on the given {@link ClientHttpRequestFactory}. 181 * @param requestFactory the HTTP request factory to use 182 * @see org.springframework.http.client.SimpleClientHttpRequestFactory 183 * @see org.springframework.http.client.HttpComponentsClientHttpRequestFactory 184 */ 185 public RestTemplate(ClientHttpRequestFactory requestFactory) { 186 this(); 187 setRequestFactory(requestFactory); 188 } 189 190 /** 191 * Create a new instance of the {@link RestTemplate} using the given list of 192 * {@link HttpMessageConverter} to use. 193 * @param messageConverters the list of {@link HttpMessageConverter} to use 194 * @since 3.2.7 195 */ 196 public RestTemplate(List<HttpMessageConverter<?>> messageConverters) { 197 validateConverters(messageConverters); 198 this.messageConverters.addAll(messageConverters); 199 this.uriTemplateHandler = initUriTemplateHandler(); 200 } 201 202 203 private static DefaultUriBuilderFactory initUriTemplateHandler() { 204 DefaultUriBuilderFactory uriFactory = new DefaultUriBuilderFactory(); 205 uriFactory.setEncodingMode(EncodingMode.URI_COMPONENT); // for backwards compatibility.. 206 return uriFactory; 207 } 208 209 210 /** 211 * Set the message body converters to use. 212 * <p>These converters are used to convert from and to HTTP requests and responses. 213 */ 214 public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) { 215 validateConverters(messageConverters); 216 // Take getMessageConverters() List as-is when passed in here 217 if (this.messageConverters != messageConverters) { 218 this.messageConverters.clear(); 219 this.messageConverters.addAll(messageConverters); 220 } 221 } 222 223 private void validateConverters(List<HttpMessageConverter<?>> messageConverters) { 224 Assert.notEmpty(messageConverters, "At least one HttpMessageConverter is required"); 225 Assert.noNullElements(messageConverters, "The HttpMessageConverter list must not contain null elements"); 226 } 227 228 /** 229 * Return the list of message body converters. 230 * <p>The returned {@link List} is active and may get appended to. 231 */ 232 public List<HttpMessageConverter<?>> getMessageConverters() { 233 return this.messageConverters; 234 } 235 236 /** 237 * Set the error handler. 238 * <p>By default, RestTemplate uses a {@link DefaultResponseErrorHandler}. 239 */ 240 public void setErrorHandler(ResponseErrorHandler errorHandler) { 241 Assert.notNull(errorHandler, "ResponseErrorHandler must not be null"); 242 this.errorHandler = errorHandler; 243 } 244 245 /** 246 * Return the error handler. 247 */ 248 public ResponseErrorHandler getErrorHandler() { 249 return this.errorHandler; 250 } 251 252 /** 253 * Configure default URI variable values. This is a shortcut for: 254 * <pre class="code"> 255 * DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(); 256 * handler.setDefaultUriVariables(...); 257 * 258 * RestTemplate restTemplate = new RestTemplate(); 259 * restTemplate.setUriTemplateHandler(handler); 260 * </pre> 261 * @param uriVars the default URI variable values 262 * @since 4.3 263 */ 264 @SuppressWarnings("deprecation") 265 public void setDefaultUriVariables(Map<String, ?> uriVars) { 266 if (this.uriTemplateHandler instanceof DefaultUriBuilderFactory) { 267 ((DefaultUriBuilderFactory) this.uriTemplateHandler).setDefaultUriVariables(uriVars); 268 } 269 else if (this.uriTemplateHandler instanceof org.springframework.web.util.AbstractUriTemplateHandler) { 270 ((org.springframework.web.util.AbstractUriTemplateHandler) this.uriTemplateHandler) 271 .setDefaultUriVariables(uriVars); 272 } 273 else { 274 throw new IllegalArgumentException( 275 "This property is not supported with the configured UriTemplateHandler."); 276 } 277 } 278 279 /** 280 * Configure a strategy for expanding URI templates. 281 * <p>By default, {@link DefaultUriBuilderFactory} is used and for 282 * backwards compatibility, the encoding mode is set to 283 * {@link EncodingMode#URI_COMPONENT URI_COMPONENT}. As of 5.0.8, prefer 284 * using {@link EncodingMode#TEMPLATE_AND_VALUES TEMPLATE_AND_VALUES}. 285 * <p><strong>Note:</strong> in 5.0 the switch from 286 * {@link org.springframework.web.util.DefaultUriTemplateHandler 287 * DefaultUriTemplateHandler} (deprecated in 4.3), as the default to use, to 288 * {@link DefaultUriBuilderFactory} brings in a different default for the 289 * {@code parsePath} property (switching from false to true). 290 * @param handler the URI template handler to use 291 */ 292 public void setUriTemplateHandler(UriTemplateHandler handler) { 293 Assert.notNull(handler, "UriTemplateHandler must not be null"); 294 this.uriTemplateHandler = handler; 295 } 296 297 /** 298 * Return the configured URI template handler. 299 */ 300 public UriTemplateHandler getUriTemplateHandler() { 301 return this.uriTemplateHandler; 302 } 303 304 305 // GET 306 307 @Override 308 @Nullable 309 public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException { 310 RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); 311 HttpMessageConverterExtractor<T> responseExtractor = 312 new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); 313 return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables); 314 } 315 316 @Override 317 @Nullable 318 public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException { 319 RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); 320 HttpMessageConverterExtractor<T> responseExtractor = 321 new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); 322 return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables); 323 } 324 325 @Override 326 @Nullable 327 public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException { 328 RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); 329 HttpMessageConverterExtractor<T> responseExtractor = 330 new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); 331 return execute(url, HttpMethod.GET, requestCallback, responseExtractor); 332 } 333 334 @Override 335 public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) 336 throws RestClientException { 337 338 RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); 339 ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); 340 return nonNull(execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables)); 341 } 342 343 @Override 344 public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables) 345 throws RestClientException { 346 347 RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); 348 ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); 349 return nonNull(execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables)); 350 } 351 352 @Override 353 public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException { 354 RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); 355 ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); 356 return nonNull(execute(url, HttpMethod.GET, requestCallback, responseExtractor)); 357 } 358 359 360 // HEAD 361 362 @Override 363 public HttpHeaders headForHeaders(String url, Object... uriVariables) throws RestClientException { 364 return nonNull(execute(url, HttpMethod.HEAD, null, headersExtractor(), uriVariables)); 365 } 366 367 @Override 368 public HttpHeaders headForHeaders(String url, Map<String, ?> uriVariables) throws RestClientException { 369 return nonNull(execute(url, HttpMethod.HEAD, null, headersExtractor(), uriVariables)); 370 } 371 372 @Override 373 public HttpHeaders headForHeaders(URI url) throws RestClientException { 374 return nonNull(execute(url, HttpMethod.HEAD, null, headersExtractor())); 375 } 376 377 378 // POST 379 380 @Override 381 @Nullable 382 public URI postForLocation(String url, @Nullable Object request, Object... uriVariables) 383 throws RestClientException { 384 385 RequestCallback requestCallback = httpEntityCallback(request); 386 HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, headersExtractor(), uriVariables); 387 return (headers != null ? headers.getLocation() : null); 388 } 389 390 @Override 391 @Nullable 392 public URI postForLocation(String url, @Nullable Object request, Map<String, ?> uriVariables) 393 throws RestClientException { 394 395 RequestCallback requestCallback = httpEntityCallback(request); 396 HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, headersExtractor(), uriVariables); 397 return (headers != null ? headers.getLocation() : null); 398 } 399 400 @Override 401 @Nullable 402 public URI postForLocation(URI url, @Nullable Object request) throws RestClientException { 403 RequestCallback requestCallback = httpEntityCallback(request); 404 HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, headersExtractor()); 405 return (headers != null ? headers.getLocation() : null); 406 } 407 408 @Override 409 @Nullable 410 public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, 411 Object... uriVariables) throws RestClientException { 412 413 RequestCallback requestCallback = httpEntityCallback(request, responseType); 414 HttpMessageConverterExtractor<T> responseExtractor = 415 new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); 416 return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); 417 } 418 419 @Override 420 @Nullable 421 public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, 422 Map<String, ?> uriVariables) throws RestClientException { 423 424 RequestCallback requestCallback = httpEntityCallback(request, responseType); 425 HttpMessageConverterExtractor<T> responseExtractor = 426 new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); 427 return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); 428 } 429 430 @Override 431 @Nullable 432 public <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType) 433 throws RestClientException { 434 435 RequestCallback requestCallback = httpEntityCallback(request, responseType); 436 HttpMessageConverterExtractor<T> responseExtractor = 437 new HttpMessageConverterExtractor<>(responseType, getMessageConverters()); 438 return execute(url, HttpMethod.POST, requestCallback, responseExtractor); 439 } 440 441 @Override 442 public <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, 443 Class<T> responseType, Object... uriVariables) throws RestClientException { 444 445 RequestCallback requestCallback = httpEntityCallback(request, responseType); 446 ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); 447 return nonNull(execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables)); 448 } 449 450 @Override 451 public <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, 452 Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException { 453 454 RequestCallback requestCallback = httpEntityCallback(request, responseType); 455 ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); 456 return nonNull(execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables)); 457 } 458 459 @Override 460 public <T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType) 461 throws RestClientException { 462 463 RequestCallback requestCallback = httpEntityCallback(request, responseType); 464 ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); 465 return nonNull(execute(url, HttpMethod.POST, requestCallback, responseExtractor)); 466 } 467 468 469 // PUT 470 471 @Override 472 public void put(String url, @Nullable Object request, Object... uriVariables) 473 throws RestClientException { 474 475 RequestCallback requestCallback = httpEntityCallback(request); 476 execute(url, HttpMethod.PUT, requestCallback, null, uriVariables); 477 } 478 479 @Override 480 public void put(String url, @Nullable Object request, Map<String, ?> uriVariables) 481 throws RestClientException { 482 483 RequestCallback requestCallback = httpEntityCallback(request); 484 execute(url, HttpMethod.PUT, requestCallback, null, uriVariables); 485 } 486 487 @Override 488 public void put(URI url, @Nullable Object request) throws RestClientException { 489 RequestCallback requestCallback = httpEntityCallback(request); 490 execute(url, HttpMethod.PUT, requestCallback, null); 491 } 492 493 494 // PATCH 495 496 @Override 497 @Nullable 498 public <T> T patchForObject(String url, @Nullable Object request, Class<T> responseType, 499 Object... uriVariables) throws RestClientException { 500 501 RequestCallback requestCallback = httpEntityCallback(request, responseType); 502 HttpMessageConverterExtractor<T> responseExtractor = 503 new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); 504 return execute(url, HttpMethod.PATCH, requestCallback, responseExtractor, uriVariables); 505 } 506 507 @Override 508 @Nullable 509 public <T> T patchForObject(String url, @Nullable Object request, Class<T> responseType, 510 Map<String, ?> uriVariables) throws RestClientException { 511 512 RequestCallback requestCallback = httpEntityCallback(request, responseType); 513 HttpMessageConverterExtractor<T> responseExtractor = 514 new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); 515 return execute(url, HttpMethod.PATCH, requestCallback, responseExtractor, uriVariables); 516 } 517 518 @Override 519 @Nullable 520 public <T> T patchForObject(URI url, @Nullable Object request, Class<T> responseType) 521 throws RestClientException { 522 523 RequestCallback requestCallback = httpEntityCallback(request, responseType); 524 HttpMessageConverterExtractor<T> responseExtractor = 525 new HttpMessageConverterExtractor<>(responseType, getMessageConverters()); 526 return execute(url, HttpMethod.PATCH, requestCallback, responseExtractor); 527 } 528 529 530 // DELETE 531 532 @Override 533 public void delete(String url, Object... uriVariables) throws RestClientException { 534 execute(url, HttpMethod.DELETE, null, null, uriVariables); 535 } 536 537 @Override 538 public void delete(String url, Map<String, ?> uriVariables) throws RestClientException { 539 execute(url, HttpMethod.DELETE, null, null, uriVariables); 540 } 541 542 @Override 543 public void delete(URI url) throws RestClientException { 544 execute(url, HttpMethod.DELETE, null, null); 545 } 546 547 548 // OPTIONS 549 550 @Override 551 public Set<HttpMethod> optionsForAllow(String url, Object... uriVariables) throws RestClientException { 552 ResponseExtractor<HttpHeaders> headersExtractor = headersExtractor(); 553 HttpHeaders headers = execute(url, HttpMethod.OPTIONS, null, headersExtractor, uriVariables); 554 return (headers != null ? headers.getAllow() : Collections.emptySet()); 555 } 556 557 @Override 558 public Set<HttpMethod> optionsForAllow(String url, Map<String, ?> uriVariables) throws RestClientException { 559 ResponseExtractor<HttpHeaders> headersExtractor = headersExtractor(); 560 HttpHeaders headers = execute(url, HttpMethod.OPTIONS, null, headersExtractor, uriVariables); 561 return (headers != null ? headers.getAllow() : Collections.emptySet()); 562 } 563 564 @Override 565 public Set<HttpMethod> optionsForAllow(URI url) throws RestClientException { 566 ResponseExtractor<HttpHeaders> headersExtractor = headersExtractor(); 567 HttpHeaders headers = execute(url, HttpMethod.OPTIONS, null, headersExtractor); 568 return (headers != null ? headers.getAllow() : Collections.emptySet()); 569 } 570 571 572 // exchange 573 574 @Override 575 public <T> ResponseEntity<T> exchange(String url, HttpMethod method, 576 @Nullable HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) 577 throws RestClientException { 578 579 RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType); 580 ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); 581 return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables)); 582 } 583 584 @Override 585 public <T> ResponseEntity<T> exchange(String url, HttpMethod method, 586 @Nullable HttpEntity<?> requestEntity, Class<T> responseType, Map<String, ?> uriVariables) 587 throws RestClientException { 588 589 RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType); 590 ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); 591 return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables)); 592 } 593 594 @Override 595 public <T> ResponseEntity<T> exchange(URI url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, 596 Class<T> responseType) throws RestClientException { 597 598 RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType); 599 ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); 600 return nonNull(execute(url, method, requestCallback, responseExtractor)); 601 } 602 603 @Override 604 public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, 605 ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException { 606 607 Type type = responseType.getType(); 608 RequestCallback requestCallback = httpEntityCallback(requestEntity, type); 609 ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(type); 610 return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables)); 611 } 612 613 @Override 614 public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, 615 ParameterizedTypeReference<T> responseType, Map<String, ?> uriVariables) throws RestClientException { 616 617 Type type = responseType.getType(); 618 RequestCallback requestCallback = httpEntityCallback(requestEntity, type); 619 ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(type); 620 return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables)); 621 } 622 623 @Override 624 public <T> ResponseEntity<T> exchange(URI url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, 625 ParameterizedTypeReference<T> responseType) throws RestClientException { 626 627 Type type = responseType.getType(); 628 RequestCallback requestCallback = httpEntityCallback(requestEntity, type); 629 ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(type); 630 return nonNull(execute(url, method, requestCallback, responseExtractor)); 631 } 632 633 @Override 634 public <T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, Class<T> responseType) 635 throws RestClientException { 636 637 RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType); 638 ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType); 639 return nonNull(doExecute(requestEntity.getUrl(), requestEntity.getMethod(), requestCallback, responseExtractor)); 640 } 641 642 @Override 643 public <T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, ParameterizedTypeReference<T> responseType) 644 throws RestClientException { 645 646 Type type = responseType.getType(); 647 RequestCallback requestCallback = httpEntityCallback(requestEntity, type); 648 ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(type); 649 return nonNull(doExecute(requestEntity.getUrl(), requestEntity.getMethod(), requestCallback, responseExtractor)); 650 } 651 652 653 // General execution 654 655 /** 656 * {@inheritDoc} 657 * <p>To provide a {@code RequestCallback} or {@code ResponseExtractor} only, 658 * but not both, consider using: 659 * <ul> 660 * <li>{@link #acceptHeaderRequestCallback(Class)} 661 * <li>{@link #httpEntityCallback(Object)} 662 * <li>{@link #httpEntityCallback(Object, Type)} 663 * <li>{@link #responseEntityExtractor(Type)} 664 * </ul> 665 */ 666 @Override 667 @Nullable 668 public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback, 669 @Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException { 670 671 URI expanded = getUriTemplateHandler().expand(url, uriVariables); 672 return doExecute(expanded, method, requestCallback, responseExtractor); 673 } 674 675 /** 676 * {@inheritDoc} 677 * <p>To provide a {@code RequestCallback} or {@code ResponseExtractor} only, 678 * but not both, consider using: 679 * <ul> 680 * <li>{@link #acceptHeaderRequestCallback(Class)} 681 * <li>{@link #httpEntityCallback(Object)} 682 * <li>{@link #httpEntityCallback(Object, Type)} 683 * <li>{@link #responseEntityExtractor(Type)} 684 * </ul> 685 */ 686 @Override 687 @Nullable 688 public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback, 689 @Nullable ResponseExtractor<T> responseExtractor, Map<String, ?> uriVariables) 690 throws RestClientException { 691 692 URI expanded = getUriTemplateHandler().expand(url, uriVariables); 693 return doExecute(expanded, method, requestCallback, responseExtractor); 694 } 695 696 /** 697 * {@inheritDoc} 698 * <p>To provide a {@code RequestCallback} or {@code ResponseExtractor} only, 699 * but not both, consider using: 700 * <ul> 701 * <li>{@link #acceptHeaderRequestCallback(Class)} 702 * <li>{@link #httpEntityCallback(Object)} 703 * <li>{@link #httpEntityCallback(Object, Type)} 704 * <li>{@link #responseEntityExtractor(Type)} 705 * </ul> 706 */ 707 @Override 708 @Nullable 709 public <T> T execute(URI url, HttpMethod method, @Nullable RequestCallback requestCallback, 710 @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException { 711 712 return doExecute(url, method, requestCallback, responseExtractor); 713 } 714 715 /** 716 * Execute the given method on the provided URI. 717 * <p>The {@link ClientHttpRequest} is processed using the {@link RequestCallback}; 718 * the response with the {@link ResponseExtractor}. 719 * @param url the fully-expanded URL to connect to 720 * @param method the HTTP method to execute (GET, POST, etc.) 721 * @param requestCallback object that prepares the request (can be {@code null}) 722 * @param responseExtractor object that extracts the return value from the response (can be {@code null}) 723 * @return an arbitrary object, as returned by the {@link ResponseExtractor} 724 */ 725 @Nullable 726 protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, 727 @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException { 728 729 Assert.notNull(url, "URI is required"); 730 Assert.notNull(method, "HttpMethod is required"); 731 ClientHttpResponse response = null; 732 try { 733 ClientHttpRequest request = createRequest(url, method); 734 if (requestCallback != null) { 735 requestCallback.doWithRequest(request); 736 } 737 response = request.execute(); 738 handleResponse(url, method, response); 739 return (responseExtractor != null ? responseExtractor.extractData(response) : null); 740 } 741 catch (IOException ex) { 742 String resource = url.toString(); 743 String query = url.getRawQuery(); 744 resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource); 745 throw new ResourceAccessException("I/O error on " + method.name() + 746 " request for \"" + resource + "\": " + ex.getMessage(), ex); 747 } 748 finally { 749 if (response != null) { 750 response.close(); 751 } 752 } 753 } 754 755 /** 756 * Handle the given response, performing appropriate logging and 757 * invoking the {@link ResponseErrorHandler} if necessary. 758 * <p>Can be overridden in subclasses. 759 * @param url the fully-expanded URL to connect to 760 * @param method the HTTP method to execute (GET, POST, etc.) 761 * @param response the resulting {@link ClientHttpResponse} 762 * @throws IOException if propagated from {@link ResponseErrorHandler} 763 * @since 4.1.6 764 * @see #setErrorHandler 765 */ 766 protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { 767 ResponseErrorHandler errorHandler = getErrorHandler(); 768 boolean hasError = errorHandler.hasError(response); 769 if (logger.isDebugEnabled()) { 770 try { 771 int code = response.getRawStatusCode(); 772 HttpStatus status = HttpStatus.resolve(code); 773 logger.debug("Response " + (status != null ? status : code)); 774 } 775 catch (IOException ex) { 776 // ignore 777 } 778 } 779 if (hasError) { 780 errorHandler.handleError(url, method, response); 781 } 782 } 783 784 /** 785 * Return a {@code RequestCallback} that sets the request {@code Accept} 786 * header based on the given response type, cross-checked against the 787 * configured message converters. 788 */ 789 public <T> RequestCallback acceptHeaderRequestCallback(Class<T> responseType) { 790 return new AcceptHeaderRequestCallback(responseType); 791 } 792 793 /** 794 * Return a {@code RequestCallback} implementation that writes the given 795 * object to the request stream. 796 */ 797 public <T> RequestCallback httpEntityCallback(@Nullable Object requestBody) { 798 return new HttpEntityRequestCallback(requestBody); 799 } 800 801 /** 802 * Return a {@code RequestCallback} implementation that: 803 * <ol> 804 * <li>Sets the request {@code Accept} header based on the given response 805 * type, cross-checked against the configured message converters. 806 * <li>Writes the given object to the request stream. 807 * </ol> 808 */ 809 public <T> RequestCallback httpEntityCallback(@Nullable Object requestBody, Type responseType) { 810 return new HttpEntityRequestCallback(requestBody, responseType); 811 } 812 813 /** 814 * Return a {@code ResponseExtractor} that prepares a {@link ResponseEntity}. 815 */ 816 public <T> ResponseExtractor<ResponseEntity<T>> responseEntityExtractor(Type responseType) { 817 return new ResponseEntityResponseExtractor<>(responseType); 818 } 819 820 /** 821 * Return a response extractor for {@link HttpHeaders}. 822 */ 823 protected ResponseExtractor<HttpHeaders> headersExtractor() { 824 return this.headersExtractor; 825 } 826 827 private static <T> T nonNull(@Nullable T result) { 828 Assert.state(result != null, "No result"); 829 return result; 830 } 831 832 833 /** 834 * Request callback implementation that prepares the request's accept headers. 835 */ 836 private class AcceptHeaderRequestCallback implements RequestCallback { 837 838 @Nullable 839 private final Type responseType; 840 841 public AcceptHeaderRequestCallback(@Nullable Type responseType) { 842 this.responseType = responseType; 843 } 844 845 @Override 846 public void doWithRequest(ClientHttpRequest request) throws IOException { 847 if (this.responseType != null) { 848 List<MediaType> allSupportedMediaTypes = getMessageConverters().stream() 849 .filter(converter -> canReadResponse(this.responseType, converter)) 850 .flatMap(this::getSupportedMediaTypes) 851 .distinct() 852 .sorted(MediaType.SPECIFICITY_COMPARATOR) 853 .collect(Collectors.toList()); 854 if (logger.isDebugEnabled()) { 855 logger.debug("Accept=" + allSupportedMediaTypes); 856 } 857 request.getHeaders().setAccept(allSupportedMediaTypes); 858 } 859 } 860 861 private boolean canReadResponse(Type responseType, HttpMessageConverter<?> converter) { 862 Class<?> responseClass = (responseType instanceof Class ? (Class<?>) responseType : null); 863 if (responseClass != null) { 864 return converter.canRead(responseClass, null); 865 } 866 else if (converter instanceof GenericHttpMessageConverter) { 867 GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter; 868 return genericConverter.canRead(responseType, null, null); 869 } 870 return false; 871 } 872 873 private Stream<MediaType> getSupportedMediaTypes(HttpMessageConverter<?> messageConverter) { 874 return messageConverter.getSupportedMediaTypes() 875 .stream() 876 .map(mediaType -> { 877 if (mediaType.getCharset() != null) { 878 return new MediaType(mediaType.getType(), mediaType.getSubtype()); 879 } 880 return mediaType; 881 }); 882 } 883 } 884 885 886 /** 887 * Request callback implementation that writes the given object to the request stream. 888 */ 889 private class HttpEntityRequestCallback extends AcceptHeaderRequestCallback { 890 891 private final HttpEntity<?> requestEntity; 892 893 public HttpEntityRequestCallback(@Nullable Object requestBody) { 894 this(requestBody, null); 895 } 896 897 public HttpEntityRequestCallback(@Nullable Object requestBody, @Nullable Type responseType) { 898 super(responseType); 899 if (requestBody instanceof HttpEntity) { 900 this.requestEntity = (HttpEntity<?>) requestBody; 901 } 902 else if (requestBody != null) { 903 this.requestEntity = new HttpEntity<>(requestBody); 904 } 905 else { 906 this.requestEntity = HttpEntity.EMPTY; 907 } 908 } 909 910 @Override 911 @SuppressWarnings("unchecked") 912 public void doWithRequest(ClientHttpRequest httpRequest) throws IOException { 913 super.doWithRequest(httpRequest); 914 Object requestBody = this.requestEntity.getBody(); 915 if (requestBody == null) { 916 HttpHeaders httpHeaders = httpRequest.getHeaders(); 917 HttpHeaders requestHeaders = this.requestEntity.getHeaders(); 918 if (!requestHeaders.isEmpty()) { 919 requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values))); 920 } 921 if (httpHeaders.getContentLength() < 0) { 922 httpHeaders.setContentLength(0L); 923 } 924 } 925 else { 926 Class<?> requestBodyClass = requestBody.getClass(); 927 Type requestBodyType = (this.requestEntity instanceof RequestEntity ? 928 ((RequestEntity<?>)this.requestEntity).getType() : requestBodyClass); 929 HttpHeaders httpHeaders = httpRequest.getHeaders(); 930 HttpHeaders requestHeaders = this.requestEntity.getHeaders(); 931 MediaType requestContentType = requestHeaders.getContentType(); 932 for (HttpMessageConverter<?> messageConverter : getMessageConverters()) { 933 if (messageConverter instanceof GenericHttpMessageConverter) { 934 GenericHttpMessageConverter<Object> genericConverter = 935 (GenericHttpMessageConverter<Object>) messageConverter; 936 if (genericConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) { 937 if (!requestHeaders.isEmpty()) { 938 requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values))); 939 } 940 logBody(requestBody, requestContentType, genericConverter); 941 genericConverter.write(requestBody, requestBodyType, requestContentType, httpRequest); 942 return; 943 } 944 } 945 else if (messageConverter.canWrite(requestBodyClass, requestContentType)) { 946 if (!requestHeaders.isEmpty()) { 947 requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values))); 948 } 949 logBody(requestBody, requestContentType, messageConverter); 950 ((HttpMessageConverter<Object>) messageConverter).write( 951 requestBody, requestContentType, httpRequest); 952 return; 953 } 954 } 955 String message = "No HttpMessageConverter for " + requestBodyClass.getName(); 956 if (requestContentType != null) { 957 message += " and content type \"" + requestContentType + "\""; 958 } 959 throw new RestClientException(message); 960 } 961 } 962 963 private void logBody(Object body, @Nullable MediaType mediaType, HttpMessageConverter<?> converter) { 964 if (logger.isDebugEnabled()) { 965 if (mediaType != null) { 966 logger.debug("Writing [" + body + "] as \"" + mediaType + "\""); 967 } 968 else { 969 logger.debug("Writing [" + body + "] with " + converter.getClass().getName()); 970 } 971 } 972 } 973 } 974 975 976 /** 977 * Response extractor for {@link HttpEntity}. 978 */ 979 private class ResponseEntityResponseExtractor<T> implements ResponseExtractor<ResponseEntity<T>> { 980 981 @Nullable 982 private final HttpMessageConverterExtractor<T> delegate; 983 984 public ResponseEntityResponseExtractor(@Nullable Type responseType) { 985 if (responseType != null && Void.class != responseType) { 986 this.delegate = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); 987 } 988 else { 989 this.delegate = null; 990 } 991 } 992 993 @Override 994 public ResponseEntity<T> extractData(ClientHttpResponse response) throws IOException { 995 if (this.delegate != null) { 996 T body = this.delegate.extractData(response); 997 return ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).body(body); 998 } 999 else { 1000 return ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).build(); 1001 } 1002 } 1003 } 1004 1005 1006 /** 1007 * Response extractor that extracts the response {@link HttpHeaders}. 1008 */ 1009 private static class HeadersExtractor implements ResponseExtractor<HttpHeaders> { 1010 1011 @Override 1012 public HttpHeaders extractData(ClientHttpResponse response) { 1013 return response.getHeaders(); 1014 } 1015 } 1016 1017}