Riptide: Spring Boot Starter is a library that seamlessly integrates various HTTP client-side tools in the easiest and convenient way possible. It solves a recurring problem of bootstrapping and wiring different libraries together whenever interaction with a remote service is required. Spinning up new clients couldn't get any easier!
- Technology stack: Spring Boot
- Status: Beta
riptide.clients:
example:
base-url: http://example.com
connections:
connect-timeout: 150 milliseconds
socket-timeout: 100 milliseconds
time-to-live: 30 seconds
max-per-route: 16
retry:
enabled: true
fixed-delay: 50 milliseconds
max-retries: 5
circuit-breaker:
enabled: true
failure-threshold: 3 out of 5
delay: 30 seconds
success-threshold: 5 out of 5
caching:
enabled: true
shared: false
max-cache-entries: 1000
tracing:
enabled: true
tags:
peer.service: example
@Autowired
@Qualifier("example")
private Http example;
- Seamless integration of:
- Spring Boot Auto Configuration
- Automatically integrates and supports:
- Transient fault detection via Riptide: Faults
- HTTP JSON Streaming via Riptide: Stream
- Timeouts via Riptide: Failsafe
- Platform IAM OAuth tokens via Riptide: Auth
- SSL certificate pinning
- Sensible defaults
- Spring Boot 3
- Riptide
- Core
- (Apache) HTTP Client
- Compression
- Backup (optional)
- Failsafe (optional)
- Faults (optional)
- Logbook (optional)
- Micrometer (optional)
- Timeouts (optional)
- SOAP (optional)
- Tracer (optional)
Add the following dependency to your project:
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-spring-boot-starter</artifactId>
<version>${riptide.version}</version>
</dependency>
You will need to add declare the following dependencies, in order to enable some integrations and/or features:
Failsafe integration
Required for retry
, circuit-breaker
, backup-request
and timeout
support. Timeout is not to be confused with connect-timeout
and socket-timeout
, those are supported out of the box.
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-failsafe</artifactId>
<version>${riptide.version}</version>
</dependency>
Transient Fault detection
Required when transient-fault-detection
is enabled.
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-faults</artifactId>
<version>${riptide.version}</version>
</dependency>
SOAP support
Required when soap
is enabled.
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-soap</artifactId>
<version>${riptide.version}</version>
</dependency>
Logbook integration
Required when logging
is enabled.
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-logbook</artifactId>
<version>${riptide.version}</version>
</dependency>
<dependency>
<groupId>org.zalando</groupId>
<artifactId>logbook-spring-boot-autoconfigure</artifactId>
<version>${logbook.version}</version>
</dependency>
Required for auth
support.
Registers a special AuthorizationProvider that built for Zalando's Platform IAM which provides OAuth2 tokens as files in a mounted directory. See Zalando Platform IAM Integration for more details.
Registering a custom AuthorizationPlugin
or AuthorizationProvider
will override the default. Setting auth.enabled: true
is still required in that case.
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-auth</artifactId>
<version>${riptide.version}</version>
</dependency>
Micrometer integration
Required when metrics
is enabled.
Will activate Micrometer metrics support for:
- requests
- thread pools
- connection pools
- retries
- circuit breaker
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-micrometer</artifactId>
<version>${riptide.version}</version>
</dependency>
Please be aware that Micrometer, by default, doesn't expose to /metrics
.
Consult #401 for details how to bypass this.
Required when caching
is enabled.
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5-cache</artifactId>
<version>${httpclient5.version}</version>
</dependency>
Required when tracing
is enabled.
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-opentracing</artifactId>
<version>${riptide.version}</version>
</dependency>
<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-concurrent</artifactId>
<version>${opentracing-concurrent.version}</version>
</dependency>
Required when telemetry
is enabled.
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-opentelemetry</artifactId>
<version>${riptide.version}</version>
</dependency>
You can now define new clients and override default configuration in your application.yml
:
riptide:
defaults:
auth:
credentials-directory: /secrets
tracing:
enabled: true
tags:
account: ${CDP_TARGET_INFRASTRUCTURE_ACCOUNT}
zone: ${CDP_TARGET_REGION}
artifact_version: ${CDP_BUILD_VERSION}
deployment_id: ${CDP_DEPLOYMENT_ID}
clients:
example:
base-url: http://example.com
connections:
connect-timeout: 150 milliseconds
socket-timeout: 100 milliseconds
time-to-live: 30 seconds
max-per-route: 16
threads:
min-size: 4
max-size: 16
keep-alive: 1 minnute
queue-size: 0
auth:
enabled: true
transient-fault-detection.enabled: true
stack-trace-preservation.enabled: true
retry:
enabled: true
fixed-delay: 50 milliseconds
max-retries: 5
max-duration: 2 seconds
jitter: 25 milliseconds
circuit-breaker:
enabled: true
failure-threshold: 3 out of 5
failure-rate-threshold: 3 out of 5 in 5 seconds
delay: 30 seconds
success-threshold: 5 out of 5
backup-request:
enabled: true
delay: 75 milliseconds
timeouts:
enabled: true
global: 500 milliseconds
caching:
enabled: true
shared: true
directory: /var/cache/http
max-object-size: 8192 # kilobytes
max-cache-entries: 1000
heuristic:
enabled: true
coefficient: 0.1
default-life-time: 10 minutes
tracing:
tags:
peer.service: example
propagate-flow-id: true
telemetry:
enabled: true
attributes:
peer.service: example
client_id: my-app
Clients are identified by a Client ID, for instance example
in the sample above.
You can have as many clients as you want.
For a complete overview of available properties, they type and default value please refer to the following table:
Configuration | Data type | Default / Comment |
---|---|---|
riptide |
||
├── defaults |
||
│ ├── auth |
||
│ │ ├── enabled |
boolean |
false |
│ │ └── credentials-directory |
Path |
/meta/credentials |
│ ├── backup-request |
||
│ │ ├── enabled |
boolean |
false |
│ │ └── delay |
TimeSpan |
no delay |
│ ├── caching |
||
│ │ ├── enabled |
boolean |
false |
│ │ ├── shared |
boolean |
false |
│ │ ├── directory |
String |
none, in-memory caching by default |
│ │ ├── max-object-size |
int |
8192 |
│ │ ├── max-cache-entries |
int |
1000 |
│ │ └── heuristic |
If max age was not specified by the server | |
│ │ ├── enabled |
boolean |
false |
│ │ ├── coefficient |
double |
0.1 |
│ │ └── default-life-time |
TimeSpan |
0 seconds , disabled |
│ ├── certificate-pinning |
||
│ │ ├── enabled |
boolean |
false |
│ │ └── keystore |
||
│ │ ├── path |
Path |
none |
│ │ └── password |
String |
none |
│ ├── chaos |
||
│ │ ├── latency |
||
│ │ │ ├── enabled |
boolean |
false |
│ │ │ ├── probability |
double |
0.01 |
│ │ │ └── delay |
TimeSpan |
1 second |
│ │ ├── exceptions |
||
│ │ │ ├── enabled |
boolean |
false |
│ │ │ └── probability |
double |
0.001 |
│ │ └── error-responses |
||
│ │ ├── enabled |
boolean |
false |
│ │ ├── probability |
double |
0.001 |
│ │ └── status-codes |
int[] |
[500, 503] |
│ ├── circuit-breaker |
||
│ │ ├── enabled |
boolean |
false |
│ │ ├── failure-threshold |
Ratio |
none |
│ │ ├── failure-rate-threshold |
RatioInTimeSpan |
none |
│ │ ├── delay |
TimeSpan |
no delay |
│ │ └── success-threshold |
Ratio |
failure-threshold |
│ ├── connections |
||
│ │ ├── lease-request-timeout |
TimeSpan |
1 second |
│ │ ├── connect-timeout |
TimeSpan |
5 seconds |
│ │ ├── socket-timeout |
TimeSpan |
5 seconds |
│ │ ├── time-to-live |
TimeSpan |
30 seconds |
│ │ ├── max-per-route |
int |
20 |
│ │ ├── max-total |
int |
20 (or at least max-per-route ) |
│ │ └── mode |
String |
streaming (alternative is buffering ) |
│ ├── logging |
||
│ │ └── enabled |
boolean |
false |
│ ├── metrics |
||
│ │ ├── enabled |
boolean |
false |
│ │ └── tags |
Map |
none |
│ ├── request-compression |
||
│ │ └── enabled |
boolean |
false |
│ ├── retry |
||
│ │ ├── enabled |
boolean |
false |
│ │ ├── fixed-delay |
TimeSpan |
none, mutually exclusive to backoff |
│ │ ├── backoff |
none, mutually exclusive to fixed-delay |
|
│ │ │ ├── enabled |
boolean |
false |
│ │ │ ├── delay |
TimeSpan |
none, requires backoff.max-delay |
│ │ │ ├── max-delay |
TimeSpan |
none, requires backoff.delay |
│ │ │ └── delay-factor |
double |
2.0 |
│ │ ├── max-retries |
int |
none |
│ │ ├── max-duration |
TimeSpan |
5 seconds |
│ │ ├── jitter-factor |
double |
none, mutually exclusive to jitter |
│ │ └── jitter |
TimeSpan |
none, mutually exclusive to jitter-factor |
│ ├── soap |
||
│ │ ├── enabled |
boolean |
false |
│ │ └── protocol |
String |
1.1 (possible other value: 1.2 ) |
│ ├── stack-trace-preservation |
||
│ │ └── enabled |
boolean |
true |
│ ├── telemetry |
||
│ │ ├── enabled |
boolean |
false |
│ │ └── attributes |
Map |
none |
│ ├── threads |
||
│ │ ├── enabled |
boolean |
true |
│ │ ├── min-size |
int |
1 |
│ │ ├── max-size |
int |
same as connections.max-total |
│ │ ├── keep-alive |
TimeSpan |
1 minute |
│ │ └── queue-size |
int |
0 (no queue) |
│ ├── timeouts |
adds Failsafe Timeout policy, can be used in addition to connections properties to control the entire duration: from sending the request to processing the response |
|
│ │ ├── enabled |
boolean |
false |
│ │ └── global |
TimeSpan |
none |
│ ├── tracing |
||
│ │ ├── enabled |
boolean |
false |
│ │ ├── tags |
Map |
none |
│ │ └── propagate-flow-id |
boolean |
false |
│ ├── transient-fault-detection |
||
│ │ └── enabled |
boolean |
false |
│ └── url-resolution |
String |
rfc |
└── clients |
||
└── <id> |
String |
|
├── backup-request |
||
│ ├── enabled |
boolean |
see defaults |
│ └── delay |
TimeSpan |
see defaults |
├── base-url |
URI |
none |
├── caching |
see defaults |
|
│ ├── enabled |
boolean |
see defaults |
│ ├── shared |
boolean |
see defaults |
│ ├── directory |
String |
see defaults |
│ ├── max-object-size |
int |
see defaults |
│ ├── max-cache-entries |
int |
see defaults |
│ └── heuristic |
||
│ ├── enabled |
boolean |
see defaults |
│ ├── coefficient |
double |
see defaults |
│ └── default-life-time |
TimeSpan |
see defaults |
├── certificate-pinning |
||
│ ├── enabled |
boolean |
see defaults |
│ └── keystore |
||
│ ├── path |
Path |
see defaults |
│ └── password |
String |
see defaults |
├── chaos |
||
│ ├── latency |
||
│ │ ├── enabled |
boolean |
see defaults |
│ │ ├── probability |
double |
see defaults |
│ │ └── delay |
TimeSpan |
see defaults |
│ ├── exceptions |
||
│ │ ├── enabled |
boolean |
see defaults |
│ │ └── probability |
double |
see defaults |
│ └── error-responses |
||
│ ├── enabled |
boolean |
see defaults |
│ ├── probability |
double |
see defaults |
│ └── status-codes |
int[] |
see defaults |
├── circuit-breaker |
||
│ ├── enabled |
boolean |
see defaults |
│ ├── failure-threshold |
Ratio |
see defaults |
│ ├── delay |
TimeSpan |
see defaults |
│ └── success-threshold |
Ratio |
see defaults |
├── connections |
||
│ ├── lease-request-timeout |
TimeSpan |
see defaults |
│ ├── connect-timeout |
TimeSpan |
see defaults |
│ ├── socket-timeout |
TimeSpan |
see defaults |
│ ├── time-to-live |
TimeSpan |
see defaults |
│ ├── max-per-route |
int |
see defaults |
│ └── max-total |
int |
see defaults |
├── logging |
||
│ └── enabled |
boolean |
see defaults |
├── metrics |
||
│ ├── enabled |
boolean |
see defaults |
│ └── tags |
Map |
see defaults |
├── auth |
||
│ ├── enabled |
boolean |
see defaults |
│ └── credentials-directory |
Path |
see defaults |
├── request-compression |
||
│ └── enabled |
boolean |
see defaults |
├── retry |
||
│ ├── enabled |
boolean |
see defaults |
│ ├── fixed-delay |
TimeSpan |
see defaults |
│ ├── backoff |
||
│ │ ├── enabled |
boolean |
see defaults |
│ │ ├── delay |
TimeSpan |
see defaults |
│ │ ├── max-delay |
TimeSpan |
see defaults |
│ │ └── delay-factor |
double |
see defaults |
│ ├── max-retries |
int |
see defaults |
│ ├── max-duration |
TimeSpan |
see defaults |
│ ├── jitter-factor |
double |
see defaults |
│ └── jitter |
TimeSpan |
see defaults |
├── soap |
||
│ ├── enabled |
boolean |
see defaults |
│ └── protocol |
String |
see defaults |
├── stack-trace-preservation |
||
│ └── enabled |
boolean |
see defaults |
├── telemetry |
||
│ ├── enabled |
boolean |
see defaults |
│ ├── attributes |
Map |
see defaults |
├── threads |
||
│ ├── enabled |
boolean |
see defaults |
│ ├── min-size |
int |
see defaults |
│ ├── max-size |
int |
see defaults |
│ ├── keep-alive |
TimeSpan |
see defaults |
│ └── queue-size |
int |
see defaults |
├── timeouts |
||
│ ├── enabled |
boolean |
see defaults |
│ └── global |
TimeSpan |
see defaults |
├── tracing |
||
│ ├── enabled |
boolean |
see defaults |
│ ├── tags |
Map |
see defaults |
│ └── propagate-flow-id |
boolean |
see defaults |
├── transient-fault-detection |
||
│ └── enabled |
boolean |
see defaults |
└── url-resolution |
String |
see defaults |
Beware that starting with Spring Boot 1.5.x the property resolution for environment variables changes and
properties like REST_CLIENTS_EXAMPLE_BASEURL
no longer work. As an alternative applications can use the
SPRING_APPLICATION_JSON
:
export SPRING_APPLICATION_JSON='{
"riptide.clients.example.base-url": ".."
}'
After configuring your clients, as shown in the last section, you can now easily inject them:
@Autowired
@Qualifier("example")
private Http example;
All beans that are created for each client use the Client ID, in this case example
, as their qualifier.
Besides Http
, you can also alternatively inject any of the following types per client directly:
RestOperations
AsyncRestOperations
ClientHttpRequestFactory
HttpClient
ClientHttpMessageConverters
A client can be configured to only connect to trusted hosts (see
Certificate Pinning) by configuring the certificate-pinning
key. Use
keystore.path
to refer to a JKS keystore on the classpath/filesystem and (optionally) specify the passphrase via keystore.password
.
A certificate can be downloaded using:
openssl s_client -showcerts -connect www.example.com:443 < /dev/null 2> /dev/null | openssl x509 -outform PEM > example.cert
You can generate a keystore using the JDK's keytool:
keytool -importcert -file example.cert -keystore example.keystore -alias example
For every client that is defined in your configuration the following beans will be created and wired.
Legend
- green: managed beans
- blue: optionally managed beans
- yellow: managed singleton beans, i.e. shared across clients
- red: mandatory dependency
- grey: optional dependency
Every single bean in the graph can optionally be replaced by your own, custom version of it. Beans can only be
overridden by name, not by type. You can define bean name by using an appropriate method name or specify it explicitly via @Bean(name = "...")
,
you cannot use @Qualifier("...")
annotation since it doesn't change an actual bean name.
As an example, the following code would add XML support to the example
client:
@Bean
public ClientHttpMessageConverters exampleHttpMessageConverters() {
return new ClientHttpMessageConverters(singletonList(new Jaxb2RootElementHttpMessageConverter()));
}
The following code can be used if you cannot use your client name in the method name (e.g. your client name is my-client
):
@Bean(name = "my-clientCircuitBreakerExecutorService")
public ClientHttpMessageConverters httpMessageConverters() {
return new ClientHttpMessageConverters(singletonList(new Jaxb2RootElementHttpMessageConverter()));
}
As you can see, any method name can be used in the second example, since bean name is provided explicitly in the annotation.
The following table shows all beans with their respective name (for the example
client) and type:
Bean Name | Bean Type |
---|---|
exampleHttp |
Http |
exampleBaseURL |
BaseURL (effectively a Supplier<URI> ) |
exampleClientHttpRequestFactory |
ClientHttpRequestFactory |
exampleHttpMessageConverters |
ClientHttpMessageConverters |
exampleHttpClient |
HttpClient |
exampleExecutorService |
ExecutorService |
exampleFailsafePlugin |
FailsafePlugin |
exampleMicrometerPlugin |
MicrometerPlugin |
exampleOriginalStackTracePlugin |
OriginalStackTracePlugin |
examplePlugin |
Plugin (optional, additional custom plugin) |
exampleScheduledExecutorService |
ScheduledExecutorService |
exampleRetryPolicy |
RetryPolicy |
exampleCircuitBreaker |
CircuitBreaker |
exampleRetryListener |
RetryListener |
exampleFaultClassifier |
FaultClassifier |
exampleCircuitBreakerListener |
CircuitBreakerListener |
exampleAuthorizationProvider |
AuthorizationProvider |
exampleRetryPolicyExecutorService |
ExecutorService |
exampleCircuitBreakerExecutorService |
ExecutorService |
exampleBackupRequestExecutorService |
ExecutorService |
exampleTimeoutExecutorService |
ExecutorService |
If you override a bean then all of its dependencies (see the graph), will not be registered, unless required by some other bean.
You can specify ExecutorService
for each FailsafePlugin
by providing beans with the following naming convention:
exampleRetryPolicyExecutorService
, exampleCircuitBreakerExecutorService
, exampleBackupRequestExecutorService
, exampleTimeoutExecutorService
.
In case you need more than one custom plugin, please use Plugin.composite(Plugin...)
.
Similar to Spring Boot's @RestClientTest
,
@RiptideClientTest
is provided. This annotation allows for convenient testing of Riptide Http
clients.
@Component
public class GatewayService {
@Autowired
@Qualifier("example")
private Http http;
void remoteCall() {
http.get("/bar").dispatch(status(), on(OK).call(pass())).join();
}
}
@RunWith(SpringRunner.class)
@RiptideClientTest(GatewayService.class)
final class RiptideTest {
@Autowired
private GatewayService client;
@Autowired
private MockRestServiceServer server;
@Test
public void shouldAutowireMockedHttp() {
server.expect(requestTo("https://example.com/bar")).andRespond(withSuccess());
client.remoteCall();
server.verify();
}
}
Beware that all components of a client below and including ClientHttpRequestFactory
are replaced by mocks. In addition, if enabled the AuthorizationProvider
will default to a mock implementation for tests.
If you have questions, concerns, bug reports, etc., please file an issue in this repository's Issue Tracker.
To contribute, simply make a pull request and add a brief description (1-2 sentences) of your addition or change. For more details, check the contribution guidelines.
In case you don't want to use this Spring Boot Starter you always have the possibility to wire everything up by hand. Feel free to take a look at this example.