Skip to content

Latest commit

 

History

History

riptide-spring-boot-starter

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

Riptide: Spring Boot Starter

Leafs

Javadoc Maven Central

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

Example

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;

Features

Dependencies

  • 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)

Installation

Add the following dependency to your project:

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>riptide-spring-boot-starter</artifactId>
    <version>${riptide.version}</version>
</dependency>

Optional Dependencies

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>

Authorization support

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.

Caching

Required when caching is enabled.

<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5-cache</artifactId>
    <version>${httpclient5.version}</version>
</dependency>

OpenTracing

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>

OpenTelemetry

Required when telemetry is enabled.

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>riptide-opentelemetry</artifactId>
    <version>${riptide.version}</version>
</dependency>

Configuration

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.

Reference

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": ".."
}'

Usage

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

Certificate Pinning

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

Customization

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

Bean Graph

Client Dependency Graph

Plugins

Client Dependency Graph

Converters

Client Dependency Graph

Interceptors

Client Dependency Graph

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...).

Testing

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.

Getting Help

If you have questions, concerns, bug reports, etc., please file an issue in this repository's Issue Tracker.

Getting Involved/Contributing

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.

Alternatives

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.