diff --git a/framework-docs/modules/ROOT/pages/integration/rest-clients.adoc b/framework-docs/modules/ROOT/pages/integration/rest-clients.adoc index eac23a16cd9..66e0ca191af 100644 --- a/framework-docs/modules/ROOT/pages/integration/rest-clients.adoc +++ b/framework-docs/modules/ROOT/pages/integration/rest-clients.adoc @@ -3,10 +3,10 @@ The Spring Framework provides the following choices for making calls to REST endpoints: -* xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] - synchronous client with a fluent API. -* xref:integration/rest-clients.adoc#rest-webclient[`WebClient`] - non-blocking, reactive client with fluent API. -* xref:integration/rest-clients.adoc#rest-resttemplate[`RestTemplate`] - synchronous client with template method API. -* xref:integration/rest-clients.adoc#rest-http-interface[HTTP Interface] - annotated interface with generated, dynamic proxy implementation. +* xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] -- synchronous client with a fluent API +* xref:integration/rest-clients.adoc#rest-webclient[`WebClient`] -- non-blocking, reactive client with fluent API +* xref:integration/rest-clients.adoc#rest-resttemplate[`RestTemplate`] -- synchronous client with template method API +* xref:integration/rest-clients.adoc#rest-http-interface[HTTP Interface Clients] -- annotated interface backed by generated proxy [[rest-restclient]] @@ -857,15 +857,17 @@ It can be used to migrate from the latter to the former. [[rest-http-interface]] -== HTTP Interface +== HTTP Interface Clients -The Spring Framework lets you define an HTTP service as a Java interface with -`@HttpExchange` methods. You can pass such an interface to `HttpServiceProxyFactory` -to create a proxy which performs requests through an HTTP client such as `RestClient` -or `WebClient`. You can also implement the interface from an `@Controller` for server -request handling. +You can define an HTTP Service as a Java interface with `@HttpExchange` methods, and use +`HttpServiceProxyFactory` to create a client proxy from it for remote access over HTTP via +`RestClient`, `WebClient`, or `RestTemplate`. On the server side, an `@Controller` class +can implement the same interface to handle requests with +xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-httpexchange-annotation[@HttpExchange] +controller methods. -Start by creating the interface with `@HttpExchange` methods: + +First, create the Java interface: [source,java,indent=0,subs="verbatim,quotes"] ---- @@ -879,69 +881,64 @@ Start by creating the interface with `@HttpExchange` methods: } ---- -Now you can create a proxy that performs requests when methods are called. - -For `RestClient`: +Optionally, use `@HttpExchange` at the type level to declare common attributes for all methods: [source,java,indent=0,subs="verbatim,quotes"] ---- - RestClient restClient = RestClient.builder().baseUrl("https://api.github.com/").build(); - RestClientAdapter adapter = RestClientAdapter.create(restClient); - HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build(); + @HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json") + public interface RepositoryService { - RepositoryService service = factory.createClient(RepositoryService.class); ----- + @GetExchange + Repository getRepository(@PathVariable String owner, @PathVariable String repo); -For `WebClient`: + @PatchExchange(contentType = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + void updateRepository(@PathVariable String owner, @PathVariable String repo, + @RequestParam String name, @RequestParam String description, @RequestParam String homepage); -[source,java,indent=0,subs="verbatim,quotes"] + } ---- - WebClient webClient = WebClient.builder().baseUrl("https://api.github.com/").build(); - WebClientAdapter adapter = WebClientAdapter.create(webClient); - HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build(); - RepositoryService service = factory.createClient(RepositoryService.class); ----- -For `RestTemplate`: +Next, configure the client and create the `HttpServiceProxyFactory`: [source,java,indent=0,subs="verbatim,quotes"] ---- + // Using RestClient... + + RestClient restClient = RestClient.create("..."); + RestClientAdapter adapter = RestClientAdapter.create(restClient); + + // or WebClient... + + WebClient webClient = WebClient.create("..."); + WebClientAdapter adapter = WebClientAdapter.create(webClient); + + // or RestTemplate... + RestTemplate restTemplate = new RestTemplate(); - restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory("https://api.github.com/")); RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate); - HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build(); - RepositoryService service = factory.createClient(RepositoryService.class); + HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build(); ---- -`@HttpExchange` is supported at the type level where it applies to all methods: +Now, you're ready to create client proxies: [source,java,indent=0,subs="verbatim,quotes"] ---- - @HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json") - public interface RepositoryService { - - @GetExchange - Repository getRepository(@PathVariable String owner, @PathVariable String repo); - - @PatchExchange(contentType = MediaType.APPLICATION_FORM_URLENCODED_VALUE) - void updateRepository(@PathVariable String owner, @PathVariable String repo, - @RequestParam String name, @RequestParam String description, @RequestParam String homepage); - - } + RepositoryService service = factory.createClient(RepositoryService.class); + // Use service methods for remote calls... ---- + [[rest-http-interface-method-parameters]] === Method Parameters -Annotated, HTTP exchange methods support flexible method signatures with the following -method parameters: +`@HttpExchange` methods support flexible method signatures with the following inputs: [cols="1,2", options="header"] |=== -| Method argument | Description +| Method parameter | Description | `URI` | Dynamically set the URL for the request, overriding the annotation's `url` attribute. @@ -1005,26 +1002,26 @@ parameter annotation) is set to `false`, or the parameter is marked optional as [[rest-http-interface.custom-resolver]] -=== Custom argument resolver +=== Custom Arguments -For more complex cases, HTTP interfaces do not support `RequestEntity` types as method parameters. -This would take over the entire HTTP request and not improve the semantics of the interface. -Instead of adding many method parameters, developers can combine them into a custom type -and configure a dedicated `HttpServiceArgumentResolver` implementation. - -In the following HTTP interface, we are using a custom `Search` type as a parameter: +You can configure a custom `HttpServiceArgumentResolver`. The example interface below +uses a custom `Search` method parameter type: include-code::./CustomHttpServiceArgumentResolver[tag=httpinterface,indent=0] -We can implement our own `HttpServiceArgumentResolver` that supports our custom `Search` type -and writes its data in the outgoing HTTP request. +A custom argument resolver could be implemented like this: include-code::./CustomHttpServiceArgumentResolver[tag=argumentresolver,indent=0] -Finally, we can use this argument resolver during the setup and use our HTTP interface. +To configure the custom argument resolver: include-code::./CustomHttpServiceArgumentResolver[tag=usage,indent=0] +TIP: By default, `RequestEntity` is not supported as a method parameter, instead encouraging +the use of more fine-grained method parameters for individual parts of the request. + + + [[rest-http-interface-return-values]] === Return Values @@ -1101,61 +1098,35 @@ underlying HTTP client, which operates at a lower level and provides more contro [[rest-http-interface-exceptions]] === Error Handling -To customize error response handling, you need to configure the underlying HTTP client. - -For `RestClient`: - -By default, `RestClient` raises `RestClientException` for 4xx and 5xx HTTP status codes. -To customize this, register a response status handler that applies to all responses -performed through the client: +To customize error handling for HTTP Service client proxies, you can configure the +underlying client as needed. By default, clients raise an exception for 4xx and 5xx HTTP +status codes. To customize this, register a response status handler that applies to all +responses performed through the client as follows: [source,java,indent=0,subs="verbatim,quotes"] ---- + // For RestClient RestClient restClient = RestClient.builder() .defaultStatusHandler(HttpStatusCode::isError, (request, response) -> ...) .build(); - RestClientAdapter adapter = RestClientAdapter.create(restClient); - HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build(); ----- - -For more details and options, such as suppressing error status codes, see the Javadoc of -`defaultStatusHandler` in `RestClient.Builder`. - -For `WebClient`: - -By default, `WebClient` raises `WebClientResponseException` for 4xx and 5xx HTTP status codes. -To customize this, register a response status handler that applies to all responses -performed through the client: -[source,java,indent=0,subs="verbatim,quotes"] ----- + // or for WebClient... WebClient webClient = WebClient.builder() .defaultStatusHandler(HttpStatusCode::isError, resp -> ...) .build(); - WebClientAdapter adapter = WebClientAdapter.create(webClient); - HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(adapter).build(); ----- -For more details and options, such as suppressing error status codes, see the Javadoc of -`defaultStatusHandler` in `WebClient.Builder`. - -For `RestTemplate`: - -By default, `RestTemplate` raises `RestClientException` for 4xx and 5xx HTTP status codes. -To customize this, register an error handler that applies to all responses -performed through the client: - -[source,java,indent=0,subs="verbatim,quotes"] ----- + // or for RestTemplate... RestTemplate restTemplate = new RestTemplate(); restTemplate.setErrorHandler(myErrorHandler); - + RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate); + HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build(); ---- -For more details and options, see the Javadoc of `setErrorHandler` in `RestTemplate` and -the `ResponseErrorHandler` hierarchy. +For more details and options such as suppressing error status codes, see the reference +documentation for each client, as well as the Javadoc of `defaultStatusHandler` in +`RestClient.Builder` or `WebClient.Builder`, and the `setErrorHandler` of `RestTemplate`.