Hi Andriy,

I wrote a multi-threaded test based on
http://svn.apache.org/repos/asf/cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultithreadedClientTest.java.
It creates 9 threads each executing 9 API requests. The test execution looks
like (step 5 and 6 are executed multi-threaded):

        1.Create server endpoint
        2.Create threadsafe API client
        3.Create 9 credentials (credential is a person having the right to 
access a
'specified' company)
        4.Obtain accessTokens for each credential from the server endpoint (so 
also
9 accessTokens are available)

        5.Create 9 threads (one for each credential)
        6.Within each thread validate each of the 9 accessTokens for the 
passed-on
credential by sending a validateAccesstoken request to the server. So 8 will
say 'NOT OK', one will say 'OK'.

All communication with the server endpoint is done through the singleton
instance of the threadsafe API client.
All 9 threads with in total 9*9=81 API requests complete as expected.

The test isn't similar to the JAXRSMultithreadedClientTest test, in the
sense that it doesn't verify the round-trip modification of HTTP headers by
each API invocation. I.e. this specific client (in the
meantime I have 5 different JAXRS clients developed, one for each supporting
interface) doesn't set specific HTTP headers, only query parameters for each
API invocation.

Do you think it would be needed to verify whether headers are passed
correctly for each API invocation?

Regards,

J.P.

-----Original Message-----
From: Andriy Redko <drr...@gmail.com>
Sent: vrijdag 30 juni 2023 3:59
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,


Yes, I believe that call to:

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

is not needed (since as you rightly pointed out, SodexoApi is already thread
safe proxy). Folks, correct us here if that is not the case.

Thank you.

Best Regards,
    Andriy Redko


> 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
> (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_b
> earer_issuer);
>                         String audience =
> Configuration.getInstance().getItem(EmittentProperties.emit_security_b
> earer_audience);
>                         String jws =
> BearerUtils.createJwtSignedJose(BearerUtils.SDX_HEADER_TYPE,issuer,aud
> ience, 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()).b
> uild();
>                 }
>         }
> 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-Thre
> adSafety
>
> Best Regards,
>     Andriy Redko

JPU>>  Apache CXF JAX-RS threadsafe clients  Hi Andriy,  I am struggling
JPU>>to understand threadsafety when creating/using  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  o       We need
JPU>>to set TLS-client parameters  2.      Then (re-)use this client in
JPU>>a threadsafe way for multiple  (possibly concurrent) invocations of
JPU>>service methods  So step ‘1.’ I have done by creating a static
JPU>>threadsafe proxy  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  Currently, in my service method I create a
JPU>>client as follows:
JPU>> SodexoApi* clientProxy* =
JPU>> JAXRSClientFactory.*fromClient*(WebClient.*client*
JPU>> (*getClientProxy*()), SodexoApi.*class*);  Here getClientProxy()
JPU>>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
JPU>>CN  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
JPU>>certificate  should be included in the default ca-certs trust
JPU>>keystore
JPU>>                 X509TrustManager tm =
JPU>>TrustManagerUtils.getValidateServerCertificateTrustManager();
JPU>>                 params.setTrustManagers(new TrustManager[] { tm
JPU>>});


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
JPU>>operationId,  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>>
JPU>>Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()
JPU>> ).build();
JPU>>                 }
JPU>>         }
JPU>> }

Reply via email to