|
|
|
@ -3,10 +3,10 @@ |
|
|
|
|
|
|
|
|
|
|
|
The Spring Framework provides the following choices for making calls to REST endpoints: |
|
|
|
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-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-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-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-http-interface[HTTP Interface Clients] -- annotated interface backed by generated proxy |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[[rest-restclient]] |
|
|
|
[[rest-restclient]] |
|
|
|
@ -857,15 +857,17 @@ It can be used to migrate from the latter to the former. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[[rest-http-interface]] |
|
|
|
[[rest-http-interface]] |
|
|
|
== HTTP Interface |
|
|
|
== HTTP Interface Clients |
|
|
|
|
|
|
|
|
|
|
|
The Spring Framework lets you define an HTTP service as a Java interface with |
|
|
|
You can define an HTTP Service as a Java interface with `@HttpExchange` methods, and use |
|
|
|
`@HttpExchange` methods. You can pass such an interface to `HttpServiceProxyFactory` |
|
|
|
`HttpServiceProxyFactory` to create a client proxy from it for remote access over HTTP via |
|
|
|
to create a proxy which performs requests through an HTTP client such as `RestClient` |
|
|
|
`RestClient`, `WebClient`, or `RestTemplate`. On the server side, an `@Controller` class |
|
|
|
or `WebClient`. You can also implement the interface from an `@Controller` for server |
|
|
|
can implement the same interface to handle requests with |
|
|
|
request handling. |
|
|
|
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"] |
|
|
|
[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. |
|
|
|
Optionally, use `@HttpExchange` at the type level to declare common attributes for all methods: |
|
|
|
|
|
|
|
|
|
|
|
For `RestClient`: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes"] |
|
|
|
[source,java,indent=0,subs="verbatim,quotes"] |
|
|
|
---- |
|
|
|
---- |
|
|
|
RestClient restClient = RestClient.builder().baseUrl("https://api.github.com/").build(); |
|
|
|
@HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json") |
|
|
|
RestClientAdapter adapter = RestClientAdapter.create(restClient); |
|
|
|
public interface RepositoryService { |
|
|
|
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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"] |
|
|
|
[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 restTemplate = new RestTemplate(); |
|
|
|
restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory("https://api.github.com/")); |
|
|
|
|
|
|
|
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate); |
|
|
|
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"] |
|
|
|
[source,java,indent=0,subs="verbatim,quotes"] |
|
|
|
---- |
|
|
|
---- |
|
|
|
@HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json") |
|
|
|
RepositoryService service = factory.createClient(RepositoryService.class); |
|
|
|
public interface RepositoryService { |
|
|
|
// Use service methods for remote calls... |
|
|
|
|
|
|
|
|
|
|
|
@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); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
---- |
|
|
|
---- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[[rest-http-interface-method-parameters]] |
|
|
|
[[rest-http-interface-method-parameters]] |
|
|
|
=== Method Parameters |
|
|
|
=== Method Parameters |
|
|
|
|
|
|
|
|
|
|
|
Annotated, HTTP exchange methods support flexible method signatures with the following |
|
|
|
`@HttpExchange` methods support flexible method signatures with the following inputs: |
|
|
|
method parameters: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[cols="1,2", options="header"] |
|
|
|
[cols="1,2", options="header"] |
|
|
|
|=== |
|
|
|
|=== |
|
|
|
| Method argument | Description |
|
|
|
| Method parameter | Description |
|
|
|
|
|
|
|
|
|
|
|
| `URI` |
|
|
|
| `URI` |
|
|
|
| Dynamically set the URL for the request, overriding the annotation's `url` attribute. |
|
|
|
| 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]] |
|
|
|
[[rest-http-interface.custom-resolver]] |
|
|
|
=== Custom argument resolver |
|
|
|
=== Custom Arguments |
|
|
|
|
|
|
|
|
|
|
|
For more complex cases, HTTP interfaces do not support `RequestEntity` types as method parameters. |
|
|
|
You can configure a custom `HttpServiceArgumentResolver`. The example interface below |
|
|
|
This would take over the entire HTTP request and not improve the semantics of the interface. |
|
|
|
uses a custom `Search` method parameter type: |
|
|
|
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: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
include-code::./CustomHttpServiceArgumentResolver[tag=httpinterface,indent=0] |
|
|
|
include-code::./CustomHttpServiceArgumentResolver[tag=httpinterface,indent=0] |
|
|
|
|
|
|
|
|
|
|
|
We can implement our own `HttpServiceArgumentResolver` that supports our custom `Search` type |
|
|
|
A custom argument resolver could be implemented like this: |
|
|
|
and writes its data in the outgoing HTTP request. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
include-code::./CustomHttpServiceArgumentResolver[tag=argumentresolver,indent=0] |
|
|
|
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] |
|
|
|
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]] |
|
|
|
[[rest-http-interface-return-values]] |
|
|
|
=== 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]] |
|
|
|
[[rest-http-interface-exceptions]] |
|
|
|
=== Error Handling |
|
|
|
=== Error Handling |
|
|
|
|
|
|
|
|
|
|
|
To customize error response handling, you need to configure the underlying HTTP 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 |
|
|
|
For `RestClient`: |
|
|
|
status codes. To customize this, register a response status handler that applies to all |
|
|
|
|
|
|
|
responses performed through the client as follows: |
|
|
|
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: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes"] |
|
|
|
[source,java,indent=0,subs="verbatim,quotes"] |
|
|
|
---- |
|
|
|
---- |
|
|
|
|
|
|
|
// For RestClient |
|
|
|
RestClient restClient = RestClient.builder() |
|
|
|
RestClient restClient = RestClient.builder() |
|
|
|
.defaultStatusHandler(HttpStatusCode::isError, (request, response) -> ...) |
|
|
|
.defaultStatusHandler(HttpStatusCode::isError, (request, response) -> ...) |
|
|
|
.build(); |
|
|
|
.build(); |
|
|
|
|
|
|
|
|
|
|
|
RestClientAdapter adapter = RestClientAdapter.create(restClient); |
|
|
|
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`: |
|
|
|
// or 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"] |
|
|
|
|
|
|
|
---- |
|
|
|
|
|
|
|
WebClient webClient = WebClient.builder() |
|
|
|
WebClient webClient = WebClient.builder() |
|
|
|
.defaultStatusHandler(HttpStatusCode::isError, resp -> ...) |
|
|
|
.defaultStatusHandler(HttpStatusCode::isError, resp -> ...) |
|
|
|
.build(); |
|
|
|
.build(); |
|
|
|
|
|
|
|
|
|
|
|
WebClientAdapter adapter = WebClientAdapter.create(webClient); |
|
|
|
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. |
|
|
|
// or for RestTemplate... |
|
|
|
To customize this, register an error handler that applies to all responses |
|
|
|
|
|
|
|
performed through the client: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes"] |
|
|
|
|
|
|
|
---- |
|
|
|
|
|
|
|
RestTemplate restTemplate = new RestTemplate(); |
|
|
|
RestTemplate restTemplate = new RestTemplate(); |
|
|
|
restTemplate.setErrorHandler(myErrorHandler); |
|
|
|
restTemplate.setErrorHandler(myErrorHandler); |
|
|
|
|
|
|
|
|
|
|
|
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate); |
|
|
|
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate); |
|
|
|
|
|
|
|
|
|
|
|
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build(); |
|
|
|
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build(); |
|
|
|
---- |
|
|
|
---- |
|
|
|
|
|
|
|
|
|
|
|
For more details and options, see the Javadoc of `setErrorHandler` in `RestTemplate` and |
|
|
|
For more details and options such as suppressing error status codes, see the reference |
|
|
|
the `ResponseErrorHandler` hierarchy. |
|
|
|
documentation for each client, as well as the Javadoc of `defaultStatusHandler` in |
|
|
|
|
|
|
|
`RestClient.Builder` or `WebClient.Builder`, and the `setErrorHandler` of `RestTemplate`. |
|
|
|
|
|
|
|
|
|
|
|
|