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
connect-timeout: 150 milliseconds
socket-timeout: 100 milliseconds
connection-time-to-live: 30 seconds
max-connections-per-route: 16
retry:
fixed-delay: 50 milliseconds
max-retries: 5
circuit-breaker:
failure-threshold: 3 out of 5
delay: 30 seconds
success-threshold: 5 out of 5
@Autowired
@Qualifier("example")
private Http example;
- Seamless integration of:
- Spring Boot Auto Configuration
- Automatically integrates and supports:
- Transient fault detection via Riptide: Faults
- Backup requests via Riptide: Backup
- HTTP JSON Streaming via Riptide: Stream
- Timeouts via Riptide: Timeout
- SSL certificate pinning
- Sensible defaults
- Java 8
- Spring Boot
- Riptide
- Core
- (Apache) HTTP Client
- Backup (optional)
- Failsafe (optional)
- Faults (optional)
- Metrics (optional)
- Timeouts (optional)
- Logbook (optional)
- Tracer (optional)
- Tokens (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
and circuit-breaker
support.
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-failsafe</artifactId>
<version>${riptide.version}</version>
</dependency>
Transient Fault detection
Required when detect-transient-faults
is enabled.
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-faults</artifactId>
<version>${riptide.version}</version>
</dependency>
Required when backup-request
is configured:
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-backup</artifactId>
<version>${riptide.version}</version>
</dependency>
Timeout support
Required when timeout
is enabled. Not to be confused with connect-timeout
and socket-timeout
, those are
supported out of the box.
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-timeout</artifactId>
<version>${riptide.version}</version>
</dependency>
Logbook integration
<dependency>
<groupId>org.zalando</groupId>
<artifactId>logbook-spring-boot-starter</artifactId>
<version>${logbook.version}</version>
</dependency>
Tracer integration
<dependency>
<groupId>org.zalando</groupId>
<artifactId>tracer-spring-boot-starter</artifactId>
<version>${tracer.version}</version>
</dependency>
Required for oauth
support.
<dependency>
<groupId>org.zalando.stups</groupId>
<artifactId>stups-http-components-oauth2</artifactId>
<version>${stups-http-components-oauth2.version}</version>
</dependency>
<dependency>
<groupId>org.zalando.stups</groupId>
<artifactId>tokens</artifactId>
<!-- 0.11.0-beta-2 or higher! -->
<version>${tokens.version}</version>
</dependency>
Metrics integration
Required when record-metrics
is enabled.
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-metrics</artifactId>
<version>${riptide.version}</version>
</dependency>
You can now define new clients and override default configuration in your application.yml
:
riptide:
oauth:
access-token-url: https://auth.example.com
credentials-directory: /secrets
scheduling-period: 10 seconds
connect-timeout: 1 second
socket-timeout: 1500 milliseconds
clients:
example:
base-url: http://example.com
connect-timeout: 150 milliseconds
socket-timeout: 100 milliseconds
connection-time-to-live: 30 seconds
max-connections-per-route: 16
thread-pool:
min-size: 4
max-size: 16
keep-alive: 1 minnute
queue-size: 0
preserve-stack-trace: true
detect-transient-faults: true
retry:
fixed-delay: 50 milliseconds
max-retries: 5
max-duration: 2 second
jitter: 25 milliseconds
circuit-breaker:
failure-threshold: 3 out of 5
delay: 30 seconds
success-threshold: 5 out of 5
backup-request:
delay: 75 milliseconds
timeout: 500 milliseconds
oauth.scopes:
- example.read
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 |
||
│ ├── url-resolution |
String |
rfc , not applicable to Async/RestTemplate |
│ ├── connect-timeout |
TimeSpan |
5 seconds |
│ ├── socket-timeout |
TimeSpan |
5 seconds |
│ ├── connection-time-to-live |
TimeSpan |
30 seconds |
│ ├── max-connections-per-route |
int |
20 |
│ ├── max-connections-total |
int |
20 (or at least max-connections-per-route ) |
│ ├── thread-pool |
||
│ │ ├── min-size |
int |
1 |
│ │ ├── max-size |
int |
same as max-connections-total |
│ │ ├── keep-alive |
TimeSpan |
1 minute |
│ │ └── queue-size |
int |
0 |
│ ├── detect-transient-faults |
boolean |
false |
│ ├── preserve-stack-trace |
boolean |
true |
│ ├── record-metrics |
boolean |
false |
│ ├── retry |
||
│ │ ├── fixed-delay |
TimeSpan |
none, mutually exclusive to backoff |
│ │ ├── backoff |
none, mutually exclusive to fixed-delay |
|
│ │ │ ├── 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 |
none |
│ │ ├── jitter-factor |
double |
none, mutually exclusive to jitter |
│ │ └── jitter |
TimeSpan |
none, mutually exclusive to jitter-factor |
│ ├── circuit-breaker |
||
│ │ ├── failure-threshold |
Ratio |
none |
│ │ ├── delay |
TimeSpan |
no delay |
│ │ └── success-threshold |
Ratio |
failure-threshold |
│ ├── backup-request |
||
│ │ └── delay |
TimeSpan |
no delay |
│ └── timeout |
TimeSpan |
none |
├── oauth |
||
│ ├── access-token-url |
URI |
env var ACCESS_TOKEN_URL |
│ ├── credentials-directory |
Path |
env var CREDENTIALS_DIR |
│ ├── scheduling-period |
TimeSpan |
5 seconds |
│ ├── connetion-timeout |
TimeSpan |
1 second |
│ ├── socket-timeout |
TimeSpan |
2 seconds |
└── clients |
||
└── <id> |
String |
|
├── base-url |
URI |
none |
├── url-resolution |
String |
see defaults |
├── connect-timeout |
TimeSpan |
see defaults |
├── socket-timeout |
TimeSpan |
see defaults |
├── connection-time-to-live |
TimeSpan |
see defaults |
├── max-connections-per-route |
int |
see defaults |
├── max-connections-total |
int |
see defaults |
└── thread-pool |
||
├── min-size |
int |
see defaults |
├── max-size |
int |
see defaults |
├── keep-alive |
TimeSpan |
see defaults |
└── queue-size |
int |
see defaults |
├── oauth |
none, disables OAuth2 if omitted | |
│ └── scopes |
List<String> |
none |
├── detect-transient-faults |
boolean |
see defaults |
├── preserve-stack-trace |
boolean |
see defaults |
├── record-metrics |
boolean |
see defaults |
├── retry |
see defaults |
|
│ ├── fixed-delay |
TimeSpan |
see defaults |
│ ├── backoff |
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 |
├── circuit-breaker |
see defaults |
|
│ ├── failure-threshold |
Ratio |
see defaults |
│ ├── delay |
TimeSpan |
see defaults |
│ └── success-threshold |
Ratio |
see defaults |
├── backup-request |
||
│ └── delay |
TimeSpan |
no delay |
├── timeout |
TimeSpan |
see defaults |
├── compress-request |
boolean |
false |
└── keystore |
disables certificate pinning if omitted | |
├── path |
String |
none |
└── password |
String |
none |
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:
RestTemplate
AsyncRestTemplate
ClientHttpRequestFactory
AsyncClientHttpRequestFactory
HttpClient
ClientHttpMessageConverters
AsyncListenableTaskExecutor
A global AccessTokens
bean is also provided.
A client can be configured to only connect to trusted hosts (see
Certificate Pinning) by configuring the keystore
key. Use
keystore.path
to refer to a JKS keystore on the classpath/filesystem and (optionally) specify the passphrase via keystore.password
.
You can generate a keystore using the JDK's keytool:
./keytool -importcert -file some-cert.crt -keystore my.keystore -alias "<some-alias>"
For every client that is defined in your configuration the following graph of beans, indicated by the green color, will be created:
Regarding the other colors:
- yellow: will be created once and then shared across different clients (if needed)
- 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. As an example, the following code would add XML support to the example
client:
@Bean
@Qualifier("example")
public ClientHttpMessageConverters exampleHttpMessageConverters() {
return new ClientHttpMessageConverters(singletonList(new Jaxb2RootElementHttpMessageConverter()));
}
The following table shows all beans with their respective name (for the example
client) and type:
Bean Name | Bean Type | Configures by default |
---|---|---|
accessToken (no client prefix!) |
AccessTokens |
OAuth settings |
exampleHttpMessageConverters |
ClientHttpMessageConverters |
Text, JSON and JSON Stream |
exampleHttpClient |
HttpClient |
Interceptors and timeouts |
exampleAsyncClientHttpRequestFactory |
AsyncClientHttpRequestFactory and ClientHttpRequestFactory |
|
exampleHttp |
Http |
Base URL |
exampleRestTemplate |
RestTemplate |
Base URL |
exampleAsyncRestTemplate |
AsyncRestTemplate |
Base URL |
exampleScheduledExecutorService |
ScheduledExecutorService |
Fixed-size thread pool |
exampleFailsafePlugin |
FailsafePlugin |
Retries and circuit breaker |
exampleRetryPolicy |
RetryPolicy |
Retries |
exampleCircuitBreaker |
CircuitBreaker |
Circuit breaker |
examplePlugin |
Plugin |
n/a |
If you override a bean then all of its dependencies (see the graph), will not be registered, unless required by some other bean.
In case you need more than one custom plugin, please use Plugin.compound(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)
public class RiptideTest {
@Autowired
private GatewayService client;
@Autowired
private MockRestServiceServer server;
@Test
public void shouldAutowireMockedHttp() throws Exception {
server.expect(requestTo("https://example.com/bar")).andRespond(withSuccess());
client.remoteCall()
server.verify();
}
}
Mind that all components of a client below and including AsyncClientHttpRequestFactory
are replaced by mocks.
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.