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/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 >>> 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(); >>> } >>> } >>> }