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_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 <[email protected]>
> Sent: woensdag 28 juni 2023 23:55
> To: Jean Pierre URKENS <[email protected]>; CXF Dev List
> <[email protected]>
> 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>> }