RE: Apache CXF JAX-RS threadsafe clients

Hi Andriy,

Ok, it is clear for the 1st part, which I restructured to:

       * private** static* SodexoApi* getThreadsafeProxy*(String
baseAddress)* throws* GeneralSecurityException {

                JacksonJsonProvider* provider* =* new* JacksonJsonProvider(
*new* CustomObjectMapper());

                List<JacksonJsonProvider>* providers* =* new*
ArrayList<JacksonJsonProvider>();

                providers.add(provider);



               * final* JAXRSClientFactoryBean* factory* =* new*
JAXRSClientFactoryBean();

                factory.setAddress(baseAddress);

                factory.setServiceClass(SodexoApi.*class*);

                factory.setProviders(providers);

                factory.getOutInterceptors().add(*new*
LoggingOutInterceptor());

                factory.getInInterceptors().add(*new*
LoggingInInterceptor());

                factory.setThreadSafe(*true*);

                SodexoApi* api* = factory.create(SodexoApi.*class*);

                ClientConfiguration* config* = WebClient.*getConfig*(api);

               * addTLSClientParameters*(config.getHttpConduit());

               * return* api;

        }

You’re less clear on the 2nd part. If I look at the example
*http://svn.apache.org/repos/asf/cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultithreadedClientTest.java*
<http://svn.apache.org/repos/asf/cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultithreadedClientTest.java>
(especially the runproxies() method),

then in my service method (which I am trying to make threadsafe) I wouldn’t
need to call:

                //Get a threadsafe API client

                SodexoApi* clientProxy* = JAXRSClientFactory.*fromClient*
(WebClient.*client*(*getClientProxy*()), SodexoApi.*class*);

The* getClientProxy*() which returns a singleton instance from the*
getThreadsafeProxy*(…) method above is already threadsafe even though it is
the same proxy instance for all service invocations as state is kept in a*
ThreadLocalClientState* object.

So my service method could just look like (still need a casting to
WebClient to be able to set the authorization HTTP header):

       * public** synchronized* Response* closeAccount*(UUID operationId,
Long authorisationId, AccountKey accountKey) {

               * try* {

                        //Set the Bearer authorization header

                        String* issuer* = Configuration.*getInstance*
().getItem(EmittentProperties.*emit_security_bearer_issuer*);

                        String* audience* = Configuration.*getInstance*
().getItem(EmittentProperties.*emit_security_bearer_audience*);

                        String* jws* = BearerUtils.*createJwtSignedJose*
(BearerUtils.*SDX_HEADER_TYPE*,issuer,audience,
operationId.toString(),(PrivateKey) BearerUtils.*getKey*(KeyName.
*kmopSignPrivKey*));

                        WebClient.*client*(*getClientProxy*())

                                        .reset()


.header(HttpHeaders.*AUTHORIZATION*, "Bearer
" + jws);

                        //Send service request

                       * return**
getClientProxy*().closeAccount(operationId.toString(),
authorisationId, accountKey);

                }* catch* (GeneralSecurityException* e*) {

                        StringBuilder* bldr* =* new* StringBuilder("Internal
error processing customerFund request (")

                                        .append("operationId="
).append(operationId.toString())

                                        .append(",authorisationId="
).append(authorisationId)

                                        .append("): "
).append(e.getMessage());

                       * LOG*.error(bldr.toString(), e);

                       * return* Response.*status*(Status.
*INTERNAL_SERVER_ERROR*).entity(e.getMessage()).build();

                }

        }

Regards,

J.P.

-----Original Message-----
From: Andriy Redko <drr...@gmail.com>
Sent: woensdag 28 juni 2023 23:55
To: Jean Pierre URKENS <jean-pierre.urk...@devoteam.com>; CXF Dev List <
dev@cxf.apache.org>
Subject: Re: Apache CXF JAX-RS threadsafe clients

Hi Jean,

So the 1st part is 100% correct way to create a thread safe client proxy,
but with the 2nd one we have an issue well documented here [1]. Since you
only modify headers, the thread safe proxy should work, but you probably
could avoid using the WebClient part (just use JAXRSClientFactoryBean)

directly:

   final JAXRSClientFactoryBean factory = new JAXRSClientFactoryBean();

   factory.setServiceClass(SodexoApi.class);

   factory.setProviders(providers);

   factory.getOutInterceptors().add(new LoggingOutInterceptor());

   factory.getInInterceptors().add(new LoggingInInterceptor());

   factory.setThreadSafe(true)

   SodexoApi api = factory.create(SodexoApi.class);

   ClientConfiguration config = WebClient.getConfig(api);

   addTLSClientParameters(config.getHttpConduit());

Hope it helps!

[1]
https://cxf.apache.org/docs/jax-rs-client-api.html#JAXRSClientAPI-ThreadSafety



Best Regards,

    Andriy Redko

JPU>  Apache CXF JAX-RS threadsafe clients

JPU> Hi Andriy,

JPU> I am struggling to understand threadsafety when creating/using

JPU> JAX-RS clients.

JPU> My intention is to:

JPU> 1.      Create a client once as I think it is heavy loaded:

JPU> o       We need to add providers and interceptors

JPU> o       We need to set TLS-client parameters

JPU> 2.      Then (re-)use this client in a threadsafe way for multiple

JPU> (possibly concurrent) invocations of service methods

JPU> So step ‘1.’ I have done by creating a static  threadsafe proxy

JPU> using the JAXRSClientFactory class.

JPU> Now the problem is step ‘2.’ How do I invoke this (static) client

JPU> to make sure that:

JPU> ·       Stuff under’1.’ is reused

JPU> ·       Reset and add headers specific for this invocation

JPU> ·       all is threadsafe

JPU> Currently, in my service method I create a client as follows:

JPU> SodexoApi* clientProxy* =

JPU> JAXRSClientFactory.*fromClient*(WebClient.*client*

JPU> (*getClientProxy*()), SodexoApi.*class*);

JPU> Here getClientProxy() is the static proxy I created, covering for step
‘1.’

JPU> Stuff, that I want to re-use to create a proxy for the SodexoApi
interface.

JPU> But I am doubting whether this is the correct way.

JPU> For the moment I don’t think I’ve a problem due to the fact that

JPU> the service methods are defined as ‘synchronized’ but that comes

JPU> with a performance penalty.

JPU> Below, a stripped version of the code I am using, to help you

JPU> understanding what I am trying to do:

JPU> public class SodexoApiClientImpl {

JPU>         private static final Logger LOG =

JPU> Logger.getLogger(SodexoApiClientImpl.class);


JPU>         /** custom HEADER field name for X-KMO-OPERATION-ID */

JPU>         public static final String HEADER_OPERATION_ID =

JPU> "X-KMO-OPERATION-ID";

JPU>         /** A threadsafe proxy to serve as {@link WebClient} for

JPU> the {@link SodexoApi} interface*/

JPU>         private static SodexoApi clientProxy;

JPU>         protected static synchronized SodexoApi getClientProxy()

JPU> throws GeneralSecurityException {

JPU>                 if (clientProxy == null) {

JPU>                         //Get the base address of the service

JPU> endpoint

JPU>                         String baseAddress =

JPU> Configuration.getInstance().getItem(EmittentProperties.emmitent_ser

JPU> vice_endpoint);

JPU>                         clientProxy =

JPU> getThreadsafeProxy(baseAddress);

JPU>                 }

JPU>                 return clientProxy;

JPU>         }

JPU>         /**

JPU>          * Create a proxy for the {@link SodexoApi} service

JPU> endpoint

JPU>          *

JPU>          * @param baseAddress - The base URI of the SDX REST API
endpoint.

JPU>          * @return The {@link SodexoApi} service endpoint proxy

JPU>          *

JPU>          * @throws GeneralSecurityException - when retrieving

JPU> certificate info fails

JPU>          */

JPU>         private static SodexoApi getThreadsafeProxy(String

JPU> baseAddress) throws GeneralSecurityException {

JPU>                 JacksonJsonProvider provider = new

JPU> JacksonJsonProvider(new CustomObjectMapper());

JPU>                 List<JacksonJsonProvider> providers = new

JPU> ArrayList<JacksonJsonProvider>();

JPU>                 providers.add(provider);

JPU>                 SodexoApi api =

JPU> JAXRSClientFactory.create(baseAddress,

JPU> SodexoApi.class, providers,true);

JPU>                 Client client = WebClient.client(api);

JPU>                 ClientConfiguration config =

JPU> WebClient.getConfig(client);

JPU>                 config.getOutInterceptors().add(new

JPU> LoggingOutInterceptor());

JPU>                 config.getInInterceptors().add(new

JPU> LoggingInInterceptor());

JPU>                 addTLSClientParameters(config.getHttpConduit());

JPU>                 return api;

JPU>         }

JPU>         /**

JPU>          * Add {@link TLSClientParameters} to the {@link HTTPConduit}.

JPU>          * <p>In the Devoteam test environement the SSL

JPU> certificates of both KMOP and KmopEmittent

JPU>          * are self-signed with a CN not referring to their hostname.

JPU> Therefore in these environments

JPU>          * the check to verifiy whether the hostname matches the CN

JPU> must be disabled.</p>

JPU>          *

JPU>          * @param conduit The {@link HTTPConduit} handling the

JPU> https transport protocol.

JPU>          *

JPU>          * @throws GeneralSecurityException - when retrieving

JPU> certificate info fails

JPU>          */

JPU>         private static void addTLSClientParameters(HTTPConduit

JPU> conduit) throws GeneralSecurityException {

JPU>                 TLSClientParameters params =

JPU> conduit.getTlsClientParameters();

JPU>                 if (params == null) {

JPU>                         params = new TLSClientParameters();

JPU>                         conduit.setTlsClientParameters(params);

JPU>                 }

JPU>                 //1.0 set the trust Manager (the server certificate

JPU> should be included in the default ca-certs trust keystore

JPU>                 X509TrustManager tm =

JPU> TrustManagerUtils.getValidateServerCertificateTrustManager();

JPU>                 params.setTrustManagers(new TrustManager[] { tm });


JPU>                 //2.0 in case of m-TLS set the keyManager if

JPU> required

JPU>                 try {

JPU>                         final String keystoreType =

JPU> Configuration.getInstance().getItem(KmoPortefeuilleProperties.kmopK

JPU> eystoreType);

JPU>                         final String keystore =

JPU> Configuration.getInstance().getItem(KmoPortefeuilleProperties.kmopE

JPU> ncKeystore);

JPU>                         final String keystorePass =

JPU> Configuration.getInstance().getItem(KmoPortefeuilleProperties.kmopE

JPU> ncKeystorePass);

JPU>                         final String keyAlias =

JPU> Configuration.getInstance().getItem(KmoPortefeuilleProperties.kmopE

JPU> ncKeyAlias);

JPU>                         final String keyPass =

JPU> Configuration.getInstance().getItem(KmoPortefeuilleProperties.kmopE

JPU> ncKeyPass);

JPU>                         KeyStore ks =

JPU> KeystoreUtils.loadKS(keystoreType,

JPU> keystore, keystorePass);

JPU>                         KeyManager km =

JPU> KeyManagerUtils.createClientKeyManager(ks, keyAlias, keyPass);

JPU>                         params.setKeyManagers(new KeyManager[]

JPU> {km});

JPU>                 } catch (PropertyNotFoundException pe) {

JPU>                         //if any of the properties do not exist

JPU> then either m-TLS does not apply or there is a property

JPU> misconfiguration

JPU>                         LOG.warn("Couldn't configure KeyManagers

JPU> for the client! This either indicates that m-TLS doesnt' apply or a

JPU> property misconifguration.");

JPU>                         LOG.warn(pe.getMessage(),pe);

JPU>                 } catch ( GeneralSecurityException e) {

JPU>                         LOG.error("Couldn't configure KeyManagers

JPU> on the HTTPConduit of the JAX-RS webclient: "+e.getMessage(),e);

JPU>                         throw e;

JPU>                 }

JPU>         }

JPU>         /**

JPU>          * Sends a CloseAccount service request to the peer

JPU> endpoint service.

JPU>          *

JPU>          * @param operationId - The unique {@link UUID identifier}

JPU> for the request message

JPU>          * @param authorisationId Long - The unique identifier of

JPU> the account at the peer side

JPU>          * @param accountKey The {@link AccountKey} request entity

JPU>          * @return An {@link Response} response object.

JPU>          */

JPU>         public synchronized Response closeAccount(UUID operationId,

JPU> Long authorisationId, AccountKey accountKey) {

JPU>                 try {

JPU>                         //Get a threadsafe SodexoAPI client proxy

JPU> instance

JPU>                        * //UJ: Is this the correct way?*

JPU>                         SodexoApi clientProxy =

JPU> JAXRSClientFactory.fromClient(WebClient.client(getClientProxy()),

JPU> SodexoApi.class);


JPU>                         //Set the Bearer authorization header

JPU>                         String issuer =

JPU> Configuration.getInstance().getItem(EmittentProperties.emit_securit

JPU> y_bearer_issuer);

JPU>                         String audience =

JPU> Configuration.getInstance().getItem(EmittentProperties.emit_securit

JPU> y_bearer_audience);

JPU>                         String jws =

JPU> BearerUtils.createJwtSignedJose(BearerUtils.SDX_HEADER_TYPE,issuer,

JPU> audience,

JPU> operationId.toString(),(PrivateKey)

JPU> BearerUtils.getKey(KeyName.kmopSignPrivKey));

JPU>                         WebClient.client(clientProxy)

JPU>                                         .reset()

JPU>

JPU> .header(HttpHeaders.AUTHORIZATION,

JPU> "Bearer " + jws);

JPU>                         return

JPU> clientProxy.closeAccount(operationId.toString(), authorisationId,

JPU> accountKey);

JPU>                 } catch (GeneralSecurityException e) {

JPU>                         StringBuilder bldr = new

JPU> StringBuilder("Internal error processing customerFund request (")

JPU> .append("operationId=").append(operationId.toString())

JPU> .append(",authorisationId=").append(authorisationId)

JPU>                                         .append("):

JPU> ").append(e.getMessage());

JPU>                         LOG.error(bldr.toString(), e);

JPU>                         return

JPU> Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()

JPU> ).build();

JPU>                 }

JPU>         }

JPU> }

Reply via email to