Hi Andriy,

The JAXRSMultithreadedClientTest class from systest returns the (modified on
thread basis) input header in the response object. I am not having a
scenario like that.

However I do have clients that pass HTTP headers and for one of them I wrote
a multithreaded client test to verify HTTP header handling. Here each
service invocation within each thread assigns its own value for this HTTP
header and as far as I can see from the logs of the server endpoint this
seems to be correctly received. That's as far as my verification went, the
server doesn't return this header value in any object and it is not my
intention to modify the server implementation just to verify this.

Regards,

J.P.

-----Original Message-----
From: Andriy Redko <drr...@gmail.com>
Sent: dinsdag 4 juli 2023 22:04
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,

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/jav
>> a/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,au
>> d 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-Thr
>> e
>> 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