Usage
To take the most value from the RestClient/WebClient, we may provide it to @HttpExchange proxy factories.
As a reminder, @HttpExchange interfaces describe a REST API from the client’s point of view. The proxies mentioned above are generated implementations to consume this API. We can see it as a REST equivalent to what @Repository is for relational databases.
If the consumed REST API exposes an OpenAPI spec (using Swagger, maybe through springdoc-openapi and Swagger annotations), the @HttpExchange interfaces can be generated using the openapi-generator-maven-plugin or openapi-generator-gradle-plugin.
In other words, we can consume REST APIs with almost zero boilerplate code:
- generate the OpenAPI spec of REST APIs from their sources
- generate the
@HttpExchangeinterfaces describing how clients can consume these APIs from the OpenAPI specs - generate
@HttpExchangeproxies, providing each with aRestClient/WebClientbean auto-configured byspring-addons-starter-rest
The following describes the last point. Refer to the docs linked above to generate the OpenAPI spec from @RestController sources or to generate @HttpExchange from this spec.
Dependency
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-starter-rest</artifactId>
<version>${spring-addons.version}</version>
</dependency>
This starter declares spring-boot-restclient and spring-boot-webclient as optional, so the application must bring the client support it actually uses. Without it, the auto-configured beans fail with Type org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder not present (or its reactive equivalent):
<!-- for RestClient beans (the default in a servlet application) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-restclient</artifactId>
</dependency>
<!-- for WebClient beans (the default in a reactive application, also usable in a servlet one) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webclient</artifactId>
</dependency>
spring-boot-starter-webflux already pulls the WebClient support, so a reactive application needs nothing more.
To authorize requests with authorization.oauth2.oauth2-registration-id, spring-boot-starter-security-oauth2-client is required too (and spring-boot-starter-security-oauth2-resource-server for forward-bearer, which reads the token from the security context of a resource server).
Minimal sample
com:
c4-soft:
springaddons:
rest:
client:
keycloak-admin-client:
base-url: ${keycloak-base-uri}/admin/realms
authorization:
oauth2:
forward-bearer: true
This exposes a pre-configured bean named keycloakAdminClient. The default type of this bean is RestClient in a servlet app and WebClient in a Webflux one.
Advanced configuration samples
machin-api-base-url: http://localhost:8081
com:
c4-soft:
springaddons:
rest:
client:
# this exposes a bean named "machinClient"
machin-client:
# Easy to override in each deployment (for instance with MACHIN_API_BASE_URL environment variable)
base-url: ${machin-api-base-url}
http:
connect-timeout-millis: 1000
read-timeout-millis: 1000
# requires org.apache.httpcomponents.client5:httpclient5 to be on the class-path
client-http-request-factory-impl: http-components
# disable SSL certificates validation
# when "client-http-request-factory-impl" is "jdk", only root authority validation is disabled (not the hostname, so the certificate CN or altnames must match the URL hostame)
ssl-certificates-validation-enabled: false
# Override what is defined in HTTP_PROXY and NO_PROXY environment variables
proxy:
connect-timeout-millis: 500
enabled: true
host: proxy2.corporate.pf
non-proxy-hosts-pattern: .+\.corporate\.pf
username: spring-backend
password: secret
port: 8080
protocol: http
authorization:
oauth2:
# authorize outgoing requests with the Bearer token in the security (possible only in a resource server app)
forward-bearer: true
# this exposes a bean named "biduleClientBuilder" (mind the "expose-builder: true" below)
bidule-client:
base-url: http://localhost:${bidule-api-port}
# expose the RestClient.Builder instead of an already built RestClient.
# The "Builder" suffix is added to the default bean name ("bidule-client" -> "biduleClient" -> "biduleClientBuilder" in this case)
expose-builder: true
authorization:
oauth2:
# authorize outgoing requests with a Bearer obtained using an OAuth2 client registration
oauth2-registration-id: bidule-registration
http:
proxy:
# use HTTP_PROXY and NO_PROXY environment variables and add proxy authentication
username: spring-backend
password: secret
chose-client:
base-url: http://localhost:${chose-api-port}
# expose a WebClient instead of a RestClient in a servlet app
type: WEB_CLIENT
# change the bean name to "chose" (would be "choseClient" by default)
bean-name: chose
authorization:
# authorize outgoing requests with Basic auth
basic:
username: spring-backend
password: secret
http:
proxy:
# Ignore HTTP_PROXY environment variable
enabled: false
chouette-client:
base-url: https://something.pf/api
# add headers with static values
headers:
X-API-KEY: change-me
X-MULTI-VALUED-HEADER:
- foo
- bar
- bam
The biduleClientBuilder bean can be used to define a biduleClient bean as follows:
/**
* @param biduleClientBuilder pre-configured using application properties
* @return a {@link RestClient} bean named "biduleClient"
*/
@Bean
RestClient biduleClient(RestClient.Builder biduleClientBuilder) throws Exception {
// Fine-tune biduleClientBuilder configuration
return biduleClientBuilder.build();
}
Backing @ImportHttpServices groups with an auto-configured client
Since Spring Framework 7, @ImportHttpServices registers @HttpExchange proxies organized in named groups, each with its own RestClient/WebClient. spring-addons-starter-rest can back such a group with an already auto-configured client from com.c4-soft.springaddons.rest.client.
com:
c4-soft:
springaddons:
rest:
client:
keycloak-client:
base-url: https://${cn}:3643/auth
ssl-bundle: self-signed
headers:
Accept:
- application/json
- application/problem+json
authorization:
oauth2:
oauth2-registration-id: rest-api
group:
keycloak-group:
client: keycloak-client
@Configuration
@ImportHttpServices(group = "keycloak-group", types = {UsersApi.class, ClientRoleMappingsApi.class})
class HttpServicesConfiguration {
}
The client keeps existing as an independently injectable bean.
Several groups can reference the same client-id, for instance to split one API’s @HttpExchange interfaces across multiple @ImportHttpServices declarations without duplicating configuration. Groups with no matching entry under com.c4-soft.springaddons.rest.group are left to Spring Boot’s own resolution (spring.http.serviceclient.* properties and any HttpServiceGroupConfigurer bean the application registers).