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