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> }