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 JAX-RS JPU> 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 using the JPU> JAXRSClientFactory class. JPU> Now the problem is step ‘2.’ How do I invoke this (static) client to make JPU> 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* = 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 the JPU> service methods are defined as ‘synchronized’ but that comes with a JPU> performance penalty. JPU> Below, a stripped version of the code I am using, to help you understanding JPU> 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 the {@link JPU> SodexoApi} interface*/ JPU> private static SodexoApi clientProxy; JPU> protected static synchronized SodexoApi getClientProxy() throws JPU> GeneralSecurityException { JPU> if (clientProxy == null) { JPU> //Get the base address of the service endpoint JPU> String baseAddress = JPU> Configuration.getInstance().getItem(EmittentProperties.emmitent_service_endpoint); JPU> clientProxy = getThreadsafeProxy(baseAddress); JPU> } JPU> return clientProxy; JPU> } JPU> /** JPU> * Create a proxy for the {@link SodexoApi} service 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 certificate JPU> info fails JPU> */ JPU> private static SodexoApi getThreadsafeProxy(String baseAddress) JPU> throws GeneralSecurityException { JPU> JacksonJsonProvider provider = new JacksonJsonProvider(new JPU> CustomObjectMapper()); JPU> List<JacksonJsonProvider> providers = new JPU> ArrayList<JacksonJsonProvider>(); JPU> providers.add(provider); JPU> SodexoApi api = JAXRSClientFactory.create(baseAddress, JPU> SodexoApi.class, providers,true); JPU> Client client = WebClient.client(api); JPU> ClientConfiguration config = WebClient.getConfig(client); JPU> config.getOutInterceptors().add(new JPU> LoggingOutInterceptor()); JPU> config.getInInterceptors().add(new 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 certificates of JPU> 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 must be JPU> disabled.</p> JPU> * JPU> * @param conduit The {@link HTTPConduit} handling the https JPU> transport protocol. JPU> * JPU> * @throws GeneralSecurityException - when retrieving certificate JPU> info fails JPU> */ JPU> private static void addTLSClientParameters(HTTPConduit conduit) JPU> 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 should JPU> 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 required JPU> try { JPU> final String keystoreType = JPU> Configuration.getInstance().getItem(KmoPortefeuilleProperties.kmopKeystoreType); JPU> final String keystore = JPU> Configuration.getInstance().getItem(KmoPortefeuilleProperties.kmopEncKeystore); JPU> final String keystorePass = JPU> Configuration.getInstance().getItem(KmoPortefeuilleProperties.kmopEncKeystorePass); JPU> final String keyAlias = JPU> Configuration.getInstance().getItem(KmoPortefeuilleProperties.kmopEncKeyAlias); JPU> final String keyPass = JPU> Configuration.getInstance().getItem(KmoPortefeuilleProperties.kmopEncKeyPass); JPU> KeyStore ks = KeystoreUtils.loadKS(keystoreType, JPU> keystore, keystorePass); JPU> KeyManager km = JPU> KeyManagerUtils.createClientKeyManager(ks, keyAlias, keyPass); JPU> params.setKeyManagers(new KeyManager[] {km}); JPU> } catch (PropertyNotFoundException pe) { JPU> //if any of the properties do not exist then either JPU> m-TLS does not apply or there is a property misconfiguration JPU> LOG.warn("Couldn't configure KeyManagers for the JPU> client! This either indicates that m-TLS doesnt' apply or a property JPU> misconifguration."); JPU> LOG.warn(pe.getMessage(),pe); JPU> } catch ( GeneralSecurityException e) { JPU> LOG.error("Couldn't configure KeyManagers on the JPU> HTTPConduit of the JAX-RS webclient: "+e.getMessage(),e); JPU> throw e; JPU> } JPU> } JPU> /** JPU> * Sends a CloseAccount service request to the peer endpoint JPU> service. JPU> * JPU> * @param operationId - The unique {@link UUID identifier} for the JPU> request message JPU> * @param authorisationId Long - The unique identifier of the JPU> 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, Long JPU> authorisationId, AccountKey accountKey) { JPU> try { JPU> //Get a threadsafe SodexoAPI client proxy 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_security_bearer_issuer); JPU> String audience = JPU> Configuration.getInstance().getItem(EmittentProperties.emit_security_bearer_audience); JPU> String jws = JPU> BearerUtils.createJwtSignedJose(BearerUtils.SDX_HEADER_TYPE,issuer,audience, JPU> operationId.toString(),(PrivateKey) JPU> BearerUtils.getKey(KeyName.kmopSignPrivKey)); JPU> WebClient.client(clientProxy) JPU> .reset() 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 StringBuilder("Internal JPU> 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()).build(); JPU> } JPU> } JPU> }