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 JPU>> Apache CXF JAX-RS threadsafe clients Hi Andriy, I am struggling JPU>>to understand threadsafety when creating/using 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 o We need JPU>>to set TLS-client parameters 2. Then (re-)use this client in JPU>>a threadsafe way for multiple (possibly concurrent) invocations of JPU>>service methods So step ‘1.’ I have done by creating a static JPU>>threadsafe proxy 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 Currently, in my service method I create a JPU>>client as follows: JPU>> SodexoApi* clientProxy* = JPU>> JAXRSClientFactory.*fromClient*(WebClient.*client* JPU>> (*getClientProxy*()), SodexoApi.*class*); Here getClientProxy() JPU>>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 JPU>>CN 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 JPU>>certificate should be included in the default ca-certs trust JPU>>keystore JPU>> X509TrustManager tm = JPU>>TrustManagerUtils.getValidateServerCertificateTrustManager(); JPU>> params.setTrustManagers(new TrustManager[] { tm JPU>>}); 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 JPU>>operationId, 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>> JPU>>Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage() JPU>> ).build(); JPU>> } JPU>> } JPU>> }