SDA Commons Client Jersey¶
The module sda-commons-client-jersey
provides support for using Jersey clients within the Dropwizard application.
Usage¶
The JerseyClientBundle
must be added to the
application. It provides a ClientFactory
to
create clients. The ClientFactory
needs to be initialized and be available in the run(...)
phase. Therefore, the
bundle should be declared as field and not in the initialize
method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
The ClientFactory
is able to create Jersey clients from interfaces defining the API with JAX-RS annotations. It may
also create generic Jersey clients that can build the request definition with a fluent API:
1 2 3 4 |
|
Configuration¶
All clients defined with the ClientFactory
can be built either for the SDA Platform or for external services.
Both SDA Platform and external clients are OpenTelemetry enabled.
Clients for the SDA Platform have some magic added that clients for an external service don't have:
-
Trace-Token
SDA Platform clients always add the Trace-Token of the current incoming request context to the headers of the outgoing request. If no incoming request context is found, a new unique Trace-Token is generated for each request. It will be discarded when the outgoing request completes.
-
Authorization
SDA Platform clients can be configured to pass through the Authorization header of an incoming request context:
- Consumer-Token1 2 3 4
clientFactory.platformClient() .enableAuthenticationPassThrough() .api(OtherSdaServiceClient.class) .atTarget("http://other-sda-service.sda.net/api");
SDA Platform clients are able to send a Consumer-Token header to identify the caller. The token that is used has to be configured and published to the bundle. Currently, the token is only the name of the consumer.
1 2
private JerseyClientBundle jerseyClientBundle = JerseyClientBundle.builder() .withConsumerTokenProvider(MyConfiguration::getConsumerToken).build();
Then the clients can be configured to add a Consumer-Token header:
1 2 3 4
clientFactory.platformClient() .enableConsumerToken() .api(OtherSdaServiceClient.class) .atTarget("http://other-sda-service.sda.net/api");
Writing API Clients as interfaces¶
Client interfaces use the same annotations as the service definitions for REST endpoints. An example is the
MockApiClient
in the integration tests
of this module.
Error handling is different based on the return type:
If a specific return type is defined (e.g. List<MyResource> getMyResources();
), it is only returned for successful
requests. In any error or redirect case, an exception is thrown. The thrown exception is a
ClientRequestException
wrapping jakarta.ws.rs.ProcessingException
or subclasses of jakarta.ws.rs.WebApplicationException
:
- jakarta.ws.rs.RedirectionException
to indicate redirects
- jakarta.ws.rs.ClientErrorException
for client errors
- jakarta.ws.rs.ServerErrorException
for server errors
If the ClientRequestException
exception is handled in the application code the application must close()
the
exception.
If a jakarta.ws.rs.core.Response
is defined as return type, HTTP errors and redirects can be read from the Response
object. Remember to always close the Response
object. It references open socket streams.
In both variants a java.net.ConnectException
may be thrown if the client can't connect to the server.
Using Jersey Client
¶
Jersey Clients can be built using the client factory for cases where the API variant with an interface is not suitable.
Jersey clients can not automatically convert jakarta.ws.rs.WebApplicationException
into our
ClientRequestException
. To avoid passing through the error the application received to the caller of the application,
the exceptions must be handled for all usages that expect a specific type as return value.
The ClientErrorUtil
can be used to
convert the exceptions. In the following example, a 4xx or 5xx response will result in a ClientRequestException
that
causes a 500 response for the incoming request:
1 2 3 |
|
If the error should be handled in the application, the exception may be caught and the error can be read from the
response.
If the ClientRequestException
is caught the implementation must close()
it.
If it is not handled or rethrown, the ClientRequestExceptionMapper
will take care about closing the underlying open
socket.
Therefore, the ClientRequestException
must not be wrapped as cause
in another exception!
1 2 3 4 5 6 7 8 9 |
|
Multipart Support¶
To support sending multipart requests like file uploads, sda-commons-shared-forms
has to be added to the project.
The client is the configured automatically to support multipart.
Concurrency¶
If you plan to use the Jersey client from another thread, note that the authorization from the request context and the trace token are missing.
This can cause issues.
You can use ContainerRequestContextHolder.transferRequestContext
to transfer the request context and MDC
to another thread.
1 2 3 |
|
HTTP Client Configuration and Proxy Support¶
Each client can be configured with the standard
Dropwizard configuration.
Please note that this requires that there is a property in your Application's Configuration
class.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
1 |
|
1 2 3 4 5 6 |
|
Tip: There is no need to make all configuration properties available as environment variables. Seldomly used properties can always be configured using System Properties.
This configuration can be used to configure a proxy server if needed. Use this if all clients should use individual proxy configurations.
In addition, each client can consume the standard proxy system properties.
Please note that a specific proxy configuration in the HttpClientConfiguration
disables the proxy system properties for the client using that configuration.
This can be helpful when all clients in an Application should use the same proxy configuration (this includes the clients that are used by the sda-commons-server-auth
bundle).
OIDC Client¶
This module also provides support for requesting OIDC access tokens from respective providers (e.g. Keycloak) for subsequent requests to other services.
Usage¶
The OidcClient
can be instantiated
with a Jersey ClientFactory
and the respectable OidcConfiguration
.
The client also implements caching that is configured according to the access tokens expiration time claim.
1 2 3 |
|
An easy way to integrate the OidcClient into existing PlatformClients is by using the provided OidcRequestFilter
and passing it to the PlatformClientBuilder:
1 2 3 4 5 6 7 8 9 10 |
|
Configuration¶
The oidc client is configured in the config.yaml
of the application.
Example config for production to be used with environment variables of the cluster configuration:
1 2 3 4 5 6 7 8 9 10 |
|
- OIDC_DISABLED
- Disable retrieving a new token completely. Not meant for production!
- Example:
false
- OIDC_GRANT_TYPE
- Sets the OIDC grant type.
- Default:
client_credentials
- Supported values:
client_credentials
- Deprecated:
password
- OIDC_CLIENT_ID
- The client id.
- Example:
client_id
- OIDC_CLIENT_SECRET
- The client secret.
- Example:
s3cr3t
- OIDC_USERNAME
- Deprecated: A username. Only used for grant type 'password'.
- Example:
john
- OIDC_PASSWORD
- Deprecated: A password. Only used for grant type 'password'.
- Example:
pa$$word
- OIDC_ISSUER_URL
- Contains the URL to the OpenID provider configuration document.
- Example:
https://<keycloak.domain>/auth/realms/<realm>
- OIDC_CACHE_DISABLED
- Disable the caching of retrieved tokens.
- Default:
false
The grant type password
is no longer allowed in OAuth2/2.1 and the support for it in this module will be deprecated at some point.
It is highly recommended to use the grant type client_credentials
.
Tips and Tricks¶
Basic Authentication¶
In order to call http endpoints which require a Basic Authentication header set you can register
the org.glassfish.jersey.client.authentication.HttpAuthenticationFeature
using the
JerseyClientBuilder.
1 2 3 4 5 6 |
|
3rd Party jakarta.ws.rs-api
Client Implementations in Classpath¶
The clients used in sda-commons require the Jersey Client implementation.
If you are facing problems with other jakarta.ws.rs-api
implementations in the classpath (e.g. RestEasy which comes
with the Keycloak SDK) the Jersey Client Builder must be propagated in your project as service.
Therefore, the service definition src/main/resources/META-INF/services/jakarta.ws.rs.client.ClientBuilder
must be added
to your project containing:
1 |
|
This works if the library that requires the other implementation does not rely on the Java ServiceLoader.
Consume API clients without catching exceptions¶
The Suppress*
annotations can be used to suppress
specific ClientRequestException
thrown in any defined API method where the return type is not a Response
.
There are configurable suppressors for
HTTP errors,
connection timeouts,
read timeouts
and all other processing errors.
Suppressed exceptions are logged at info level.
The following client will convert a 404 Not Found error to null
so that the consumer can handle
an entity that is not found by checking if the return value is null
.
Any other error will lead to a 500 Internal Server Error because the ClientRequestException
is converted in the
ClientRequestExceptionMapper
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|