Hi Jean,
 
AFAIK, conceptually, the headers and query parameters are handled the "same" 
way by the
underlying client (from state perspective). Since you already have a test, it 
would be great 
to add header verification, you have everything in place already for that, 
should be pretty 
straightforward. I don't expect surprises here but it won't hurt I believe.
Thanks.
 
Best Regards,
    Andriy Redko 

Tuesday, July 4, 2023, 4:47:59 AM, you wrote:

> 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

>>>  Apache CXF JAX-RS threadsafe clients  Hi Andriy,  I am struggling

>>> to understand threadsafety when creating/using  JAX-RS clients.
>>> My intention is to:
>>> 1.      Create a client once as I think it is heavy loaded:
>>> o       We need to add providers and interceptors  o       We need
>>> to set TLS-client parameters  2.      Then (re-)use this client in
>>> a threadsafe way for multiple  (possibly concurrent) invocations of
>>> service methods  So step ‘1.’ I have done by creating a static
>>> threadsafe proxy  using the JAXRSClientFactory class.
>>> Now the problem is step ‘2.’ How do I invoke this (static) client
>>> to make sure that:
>>> ·       Stuff under’1.’ is reused
>>> ·       Reset and add headers specific for this invocation  ·
>>> all is threadsafe  Currently, in my service method I create a
>>> client as follows:
>>> SodexoApi* clientProxy* =
>>> JAXRSClientFactory.*fromClient*(WebClient.*client*
>>> (*getClientProxy*()), SodexoApi.*class*);  Here getClientProxy()
>>> is the static proxy I created, covering for step ‘1.’
>>> Stuff, that I want to re-use to create a proxy for the SodexoApi
> interface.
>>> But I am doubting whether this is the correct way.
>>> For the moment I don’t think I’ve a problem due to the fact that

>>> the service methods are defined as ‘synchronized’ but that comes
>>> with a performance penalty.
>>> Below, a stripped version of the code I am using, to help you
>>> understanding what I am trying to do:
>>> public class SodexoApiClientImpl {
>>>         private static final Logger LOG =
>>> Logger.getLogger(SodexoApiClientImpl.class);


>>>         /** custom HEADER field name for X-KMO-OPERATION-ID */
>>>         public static final String HEADER_OPERATION_ID =

>>> "X-KMO-OPERATION-ID";
>>>         /** A threadsafe proxy to serve as {@link WebClient} for
>>> the {@link SodexoApi} interface*/
>>>         private static SodexoApi clientProxy;
>>>         protected static synchronized SodexoApi getClientProxy()
>>> throws GeneralSecurityException {
>>>                 if (clientProxy == null) {
>>>                         //Get the base address of the service
>>> endpoint
>>>                         String baseAddress =
>>> Configuration.getInstance().getItem(EmittentProperties.emmitent_ser
>>> vice_endpoint);
>>>                         clientProxy =
>>> getThreadsafeProxy(baseAddress);
>>>                 }
>>>                 return clientProxy;
>>>         }
>>>         /**
>>>          * Create a proxy for the {@link SodexoApi} service
>>> endpoint
>>>          *
>>>          * @param baseAddress - The base URI of the SDX REST API
> endpoint.
>>>          * @return The {@link SodexoApi} service endpoint proxy
>>>          *
>>>          * @throws GeneralSecurityException - when retrieving

>>> certificate info fails
>>>          */
>>>         private static SodexoApi getThreadsafeProxy(String
>>> baseAddress) throws GeneralSecurityException {
>>>                 JacksonJsonProvider provider = new
>>> JacksonJsonProvider(new CustomObjectMapper());
>>>                 List<JacksonJsonProvider> providers = new
>>> ArrayList<JacksonJsonProvider>();
>>>                 providers.add(provider);
>>>                 SodexoApi api =
>>> JAXRSClientFactory.create(baseAddress,
>>> SodexoApi.class, providers,true);
>>>                 Client client = WebClient.client(api);
>>>                 ClientConfiguration config =
>>> WebClient.getConfig(client);
>>>                 config.getOutInterceptors().add(new
>>> LoggingOutInterceptor());
>>>                 config.getInInterceptors().add(new
>>> LoggingInInterceptor());
>>>                 addTLSClientParameters(config.getHttpConduit());
>>>                 return api;
>>>         }
>>>         /**
>>>          * Add {@link TLSClientParameters} to the {@link HTTPConduit}.
>>>          * <p>In the Devoteam test environement the SSL
>>> certificates of both KMOP and KmopEmittent
>>>          * are self-signed with a CN not referring to their hostname.
>>> Therefore in these environments
>>>          * the check to verifiy whether the hostname matches the
>>> CN  must be disabled.</p>
>>>          *
>>>          * @param conduit The {@link HTTPConduit} handling the
>>> https transport protocol.
>>>          *
>>>          * @throws GeneralSecurityException - when retrieving
>>> certificate info fails
>>>          */
>>>         private static void addTLSClientParameters(HTTPConduit
>>> conduit) throws GeneralSecurityException {
>>>                 TLSClientParameters params =
>>> conduit.getTlsClientParameters();
>>>                 if (params == null) {
>>>                         params = new TLSClientParameters();
>>>                         conduit.setTlsClientParameters(params);
>>>                 }
>>>                 //1.0 set the trust Manager (the server
>>> certificate  should be included in the default ca-certs trust
>>> keystore
>>>                 X509TrustManager tm =
>>> TrustManagerUtils.getValidateServerCertificateTrustManager();
>>>                 params.setTrustManagers(new TrustManager[] { tm
>>> });


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

>>> required
>>>                 try {
>>>                         final String keystoreType =
>>> Configuration.getInstance().getItem(KmoPortefeuilleProperties.kmopK
>>> eystoreType);
>>>                         final String keystore =
>>> Configuration.getInstance().getItem(KmoPortefeuilleProperties.kmopE
>>> ncKeystore);
>>>                         final String keystorePass =
>>> Configuration.getInstance().getItem(KmoPortefeuilleProperties.kmopE
>>> ncKeystorePass);
>>>                         final String keyAlias =
>>> Configuration.getInstance().getItem(KmoPortefeuilleProperties.kmopE
>>> ncKeyAlias);
>>>                         final String keyPass =
>>> Configuration.getInstance().getItem(KmoPortefeuilleProperties.kmopE
>>> ncKeyPass);
>>>                         KeyStore ks =
>>> KeystoreUtils.loadKS(keystoreType,
>>> keystore, keystorePass);
>>>                         KeyManager km =
>>> KeyManagerUtils.createClientKeyManager(ks, keyAlias, keyPass);
>>>                         params.setKeyManagers(new KeyManager[]
>>> {km});
>>>                 } catch (PropertyNotFoundException pe) {
>>>                         //if any of the properties do not exist
>>> then either m-TLS does not apply or there is a property
>>> misconfiguration
>>>                         LOG.warn("Couldn't configure KeyManagers
>>> for the client! This either indicates that m-TLS doesnt' apply or a
>>> property misconifguration.");
>>>                         LOG.warn(pe.getMessage(),pe);
>>>                 } catch ( GeneralSecurityException e) {
>>>                         LOG.error("Couldn't configure KeyManagers
>>> on the HTTPConduit of the JAX-RS webclient: "+e.getMessage(),e);
>>>                         throw e;
>>>                 }
>>>         }
>>>         /**
>>>          * Sends a CloseAccount service request to the peer
>>> endpoint service.
>>>          *
>>>          * @param operationId - The unique {@link UUID identifier}
>>> for the request message
>>>          * @param authorisationId Long - The unique identifier of
>>> the account at the peer side
>>>          * @param accountKey The {@link AccountKey} request entity
>>>          * @return An {@link Response} response object.
>>>          */
>>>         public synchronized Response closeAccount(UUID
>>> operationId,  Long authorisationId, AccountKey accountKey) {
>>>                 try {
>>>                         //Get a threadsafe SodexoAPI client proxy
>>> instance
>>>                        * //UJ: Is this the correct way?*
>>>                         SodexoApi clientProxy =
>>> JAXRSClientFactory.fromClient(WebClient.client(getClientProxy()),
>>> SodexoApi.class);


>>>                         //Set the Bearer authorization header
>>>                         String issuer =

>>> Configuration.getInstance().getItem(EmittentProperties.emit_securit
>>> y_bearer_issuer);
>>>                         String audience =
>>> Configuration.getInstance().getItem(EmittentProperties.emit_securit
>>> y_bearer_audience);
>>>                         String jws =
>>> BearerUtils.createJwtSignedJose(BearerUtils.SDX_HEADER_TYPE,issuer,
>>> audience,
>>> operationId.toString(),(PrivateKey)
>>> BearerUtils.getKey(KeyName.kmopSignPrivKey));
>>>                         WebClient.client(clientProxy)
>>>                                         .reset()
>>> .header(HttpHeaders.AUTHORIZATION,
>>> "Bearer " + jws);
>>>                         return
>>> clientProxy.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();
>>>                 }
>>>         }
>>> }

Reply via email to