即时修改 CXF 故障转移管道 - 保证相同的 SSL 会话和客户端线程安全?
CXF Failover Conduit modified on the fly - guarantee same SSL session and client thread safety?
我想为我们应用程序中的两个 JAX-WS/RS 客户端实现 CXF 的故障转移功能,这些客户端必须使用客户端证书通过 HTTPS 调用远程 Web 服务。 2 个远程服务器已到位:主要 + 备用。
我有点不知道如何在故障转移发生后保证相同的功能(使用正确的 TLS params/SSL 会话)。
JAX-WS 客户端
有 6 个 ClientServices,它们 extend AbstractClientServiceImpl
并使用相同的 PortType wsClient
bean 和远程服务器的相同 basePath,但是它们将它们设置为使用它们自己的 [=15] 来调用服务的最后一个 uri 部分=]方法。
BasePath: https://remote1.server.com:443/api
Alternate addresses: https://remote2.server.com:443/api
请看一下代码 - ClientEndpointAddressInterceptor
。使用这个拦截器,我能够组合 basePath + lastUriPart 并为特定的 ClientService 调用正确的目标端点 - 即使发生故障转移。例如:
target endpoint for ClientService1.class https://remote1.server.com:443/api/service1
target endpoint for ClientService2.class: https://remote1.server.com:443/api/service2
我已经为正确 settings/configuration 苦苦挣扎了 2 周。
如果我不像这样将 tlsClientParameters 或 HttpClientPolicy 添加到 extensor,那么在 发生故障转移后,我将无法 看到任何新创建的管道的 TLS 设置!
// ssl settings
endpointInfo.addExtensor(tlsClientParameters);
我不知道这是否是正确的方法,但是通过这个 hacky 解决方法,我本可以 "provide" 相同的 tlsClientParams 2 个远程调用(主要 + 备用远程服务器地址)- 客户端证书的 SAN 具有两个服务器的 DNS 名称。
技术:SpringBoot v2.1 + CXF 3.3.0 + Tomcat8.5
@Bean
public PortType wsClient(Properties properties,
TLSClientParameters tlsClientParameters,
LoggingFeature loggingFeature,
ClientEndpointAddressFeature clientEndpointAddressFeature) {
return createClient(properties, huTlsClientParameters, loggingFeature,
createFailoverFeature(properties.getFailover().getAddresses(), properties.getFailover().getRetryDelay()),
clientEndpointAddressFeature);
}
private FailoverFeature createFailoverFeature(String[] alternateAddresses, long failOverRetryDelay) {
final FailoverFeature failOverFeature = new FailoverFeature();
final SequentialStrategy strategy = new SequentialStrategy();
strategy.setAlternateAddresses(Arrays.asList(alternateAddresses));
strategy.setDelayBetweenRetries(failOverRetryDelay);
failOverFeature.setStrategy(strategy);
return failOverFeature;
}
private PortType createClient(Properties properties, TLSClientParameters tlsClientParameters, WebServiceFeature... features) {
final Service service = new Service();
final PortType client = service.getPortType(features);
final Client clientProxy = ClientProxy.getClient(client);
final EndpointInfo endpointInfo = clientProxy.getEndpoint().getEndpointInfo();
final HTTPClientPolicy httpClientPolicy = Optional.ofNullable(endpointInfo.getExtensor(HTTPClientPolicy.class))
.orElseGet(() -> {
// if there is no XYFeature, policy has to be initialized at this point
final HTTPClientPolicy policy = new HTTPClientPolicy();
policy.setAccept(HuHttpHeaders.HEADER_ACCEPT_VALUE);
endpointInfo.addExtensor(policy);
return policy;
});
// timeout settings
httpClientPolicy.setConnectionTimeout(properties.getConnectionTimeout());
httpClientPolicy.setReceiveTimeout(properties.getReadTimeout());
// set content-length by default
httpClientPolicy.setAllowChunking(false);
// ssl settings
endpointInfo.addExtensor(tlsClientParameters);
// set global requestContext
setRequestContext((BindingProvider) client, properties.getUrl());
return client;
}
private void setRequestContext(BindingProvider bp, String server) {
bp.getRequestContext().put(Message.ENDPOINT_ADDRESS, server);
bp.getRequestContext().put(ClientImpl.THREAD_LOCAL_REQUEST_CONTEXT, true);
bp.getRequestContext().put(Message.SCHEMA_VALIDATION_ENABLED, true);
bp.getRequestContext().put(BindingProvider.SOAPACTION_USE_PROPERTY, true);
}
public class ClientEndpointAddressOutInterceptor extends AbstractPhaseInterceptor<Message> {
public ClientEndpointAddressOutInterceptor() {
super(Phase.PREPARE_SEND);
addBefore(MessageSenderInterceptor.class.getName());
}
@Override
public void handleMessage(Message message) throws Fault {
final String previousEndpointAddress = (String) message.get(Message.ENDPOINT_ADDRESS);
final String lastUriPath = (String) message.get("lastUriPath");
message.put(Message.ENDPOINT_ADDRESS, previousEndpointAddress + lastUriPath);
}
}
public abstract class AbstractClientServiceImpl implements ClientService {
public AbstractClientServiceImpl(PortType PortType) {
this.portType = portType;
}
@Override
public HttpStatus sendRequest(String xmlData) {
...
final BindingProvider bindingProvider = (BindingProvider) this.portType;
try {
// set http header for this particular request
// also store bindingProvider.getRequestContext().put("lastUriPath", getEndpointUrl());
HttpHeaderUtil.setHttpHeader(getSoapActionUrl(), bindingProvider, getEndpointUrl());
execute(xmlData, createSoapHeader());
} catch (Exception ex) {
...
}
...
}
// last uri part
protected abstract String getEndpointUrl();
// execute is responsible for calling a particular service. e.g: in ClientService1.class portType.callService1(xmlData);
protected abstract void execute(String xmlData, TransactionHeader transactionHeader);
}
问题
JAX-WS 客户端
- 发生故障转移后,先前设置的 (thread-local-requests,true) 设置会怎样?由 6 个 ClientServices class 调用的以下服务调用之后会保持线程安全吗?
- 我有一个要求,我应该使用 SSL 会话重用机制。如果 CXF 的故障转移功能删除了管道并创建了一个新管道,那么我如何在故障转移后再次应用它?或者 Tomcat 是否以某种方式处理了这个而无需为此烦恼?我不是 CXF 专家,我在 CXF 的 site/mailing 列表中没有找到太多与 SSL 会话相关的信息。
JAX-RS 客户端
实际上与上面为 JAX-WS 客户端解决的 2 concerns/questions 相同。
唯一的区别是 RS 有 3 个客户端调用方法使用相同的客户端实例声明为
private WebClient webClient(){
final JAXRSClientFactoryBean clientFactoryBean = new JAXRSClientFactoryBean();
clientFactoryBean.setThreadSafe(true);
final WebClient webClient = clientFactoryBean.createWebClient();
final ClientConfiguration config = WebClient.getConfig(webClient);
config.getRequestContext().put(HTTPConduit.NO_IO_EXCEPTIONS, Boolean.TRUE);
// ssl settings
config.getEndpoint().getEndpointInfo().addExtensor(tlsClientParameters);
return webClient;
}
提前感谢您的帮助。
解决方案是使用 CXF 的 HTTPConduitConfigurator,详见此处:https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=49941#ClientHTTPTransport(includingSSLsupport)-HowtouseHTTPConduitConfigurer?
HTTPConduitConfigurer httpConduitConfigurer = new HTTPConduitConfigurer() {
public void configure(String name, String address, HTTPConduit c) {
c.setTlsClientParameters(_tlsParams);
}
}
bus.setExtension(httpConduitConfigurer, HTTPConduitConfigurer.class);
这将在创建的所有管道上设置 TLS 客户端参数,例如使用故障转移时。
我想为我们应用程序中的两个 JAX-WS/RS 客户端实现 CXF 的故障转移功能,这些客户端必须使用客户端证书通过 HTTPS 调用远程 Web 服务。 2 个远程服务器已到位:主要 + 备用。
我有点不知道如何在故障转移发生后保证相同的功能(使用正确的 TLS params/SSL 会话)。
JAX-WS 客户端
有 6 个 ClientServices,它们 extend AbstractClientServiceImpl
并使用相同的 PortType wsClient
bean 和远程服务器的相同 basePath,但是它们将它们设置为使用它们自己的 [=15] 来调用服务的最后一个 uri 部分=]方法。
BasePath: https://remote1.server.com:443/api
Alternate addresses: https://remote2.server.com:443/api
请看一下代码 - ClientEndpointAddressInterceptor
。使用这个拦截器,我能够组合 basePath + lastUriPart 并为特定的 ClientService 调用正确的目标端点 - 即使发生故障转移。例如:
target endpoint for ClientService1.class https://remote1.server.com:443/api/service1
target endpoint for ClientService2.class: https://remote1.server.com:443/api/service2
我已经为正确 settings/configuration 苦苦挣扎了 2 周。 如果我不像这样将 tlsClientParameters 或 HttpClientPolicy 添加到 extensor,那么在 发生故障转移后,我将无法 看到任何新创建的管道的 TLS 设置!
// ssl settings
endpointInfo.addExtensor(tlsClientParameters);
我不知道这是否是正确的方法,但是通过这个 hacky 解决方法,我本可以 "provide" 相同的 tlsClientParams 2 个远程调用(主要 + 备用远程服务器地址)- 客户端证书的 SAN 具有两个服务器的 DNS 名称。
技术:SpringBoot v2.1 + CXF 3.3.0 + Tomcat8.5
@Bean
public PortType wsClient(Properties properties,
TLSClientParameters tlsClientParameters,
LoggingFeature loggingFeature,
ClientEndpointAddressFeature clientEndpointAddressFeature) {
return createClient(properties, huTlsClientParameters, loggingFeature,
createFailoverFeature(properties.getFailover().getAddresses(), properties.getFailover().getRetryDelay()),
clientEndpointAddressFeature);
}
private FailoverFeature createFailoverFeature(String[] alternateAddresses, long failOverRetryDelay) {
final FailoverFeature failOverFeature = new FailoverFeature();
final SequentialStrategy strategy = new SequentialStrategy();
strategy.setAlternateAddresses(Arrays.asList(alternateAddresses));
strategy.setDelayBetweenRetries(failOverRetryDelay);
failOverFeature.setStrategy(strategy);
return failOverFeature;
}
private PortType createClient(Properties properties, TLSClientParameters tlsClientParameters, WebServiceFeature... features) {
final Service service = new Service();
final PortType client = service.getPortType(features);
final Client clientProxy = ClientProxy.getClient(client);
final EndpointInfo endpointInfo = clientProxy.getEndpoint().getEndpointInfo();
final HTTPClientPolicy httpClientPolicy = Optional.ofNullable(endpointInfo.getExtensor(HTTPClientPolicy.class))
.orElseGet(() -> {
// if there is no XYFeature, policy has to be initialized at this point
final HTTPClientPolicy policy = new HTTPClientPolicy();
policy.setAccept(HuHttpHeaders.HEADER_ACCEPT_VALUE);
endpointInfo.addExtensor(policy);
return policy;
});
// timeout settings
httpClientPolicy.setConnectionTimeout(properties.getConnectionTimeout());
httpClientPolicy.setReceiveTimeout(properties.getReadTimeout());
// set content-length by default
httpClientPolicy.setAllowChunking(false);
// ssl settings
endpointInfo.addExtensor(tlsClientParameters);
// set global requestContext
setRequestContext((BindingProvider) client, properties.getUrl());
return client;
}
private void setRequestContext(BindingProvider bp, String server) {
bp.getRequestContext().put(Message.ENDPOINT_ADDRESS, server);
bp.getRequestContext().put(ClientImpl.THREAD_LOCAL_REQUEST_CONTEXT, true);
bp.getRequestContext().put(Message.SCHEMA_VALIDATION_ENABLED, true);
bp.getRequestContext().put(BindingProvider.SOAPACTION_USE_PROPERTY, true);
}
public class ClientEndpointAddressOutInterceptor extends AbstractPhaseInterceptor<Message> {
public ClientEndpointAddressOutInterceptor() {
super(Phase.PREPARE_SEND);
addBefore(MessageSenderInterceptor.class.getName());
}
@Override
public void handleMessage(Message message) throws Fault {
final String previousEndpointAddress = (String) message.get(Message.ENDPOINT_ADDRESS);
final String lastUriPath = (String) message.get("lastUriPath");
message.put(Message.ENDPOINT_ADDRESS, previousEndpointAddress + lastUriPath);
}
}
public abstract class AbstractClientServiceImpl implements ClientService {
public AbstractClientServiceImpl(PortType PortType) {
this.portType = portType;
}
@Override
public HttpStatus sendRequest(String xmlData) {
...
final BindingProvider bindingProvider = (BindingProvider) this.portType;
try {
// set http header for this particular request
// also store bindingProvider.getRequestContext().put("lastUriPath", getEndpointUrl());
HttpHeaderUtil.setHttpHeader(getSoapActionUrl(), bindingProvider, getEndpointUrl());
execute(xmlData, createSoapHeader());
} catch (Exception ex) {
...
}
...
}
// last uri part
protected abstract String getEndpointUrl();
// execute is responsible for calling a particular service. e.g: in ClientService1.class portType.callService1(xmlData);
protected abstract void execute(String xmlData, TransactionHeader transactionHeader);
}
问题
JAX-WS 客户端
- 发生故障转移后,先前设置的 (thread-local-requests,true) 设置会怎样?由 6 个 ClientServices class 调用的以下服务调用之后会保持线程安全吗?
- 我有一个要求,我应该使用 SSL 会话重用机制。如果 CXF 的故障转移功能删除了管道并创建了一个新管道,那么我如何在故障转移后再次应用它?或者 Tomcat 是否以某种方式处理了这个而无需为此烦恼?我不是 CXF 专家,我在 CXF 的 site/mailing 列表中没有找到太多与 SSL 会话相关的信息。
JAX-RS 客户端
实际上与上面为 JAX-WS 客户端解决的 2 concerns/questions 相同。
唯一的区别是 RS 有 3 个客户端调用方法使用相同的客户端实例声明为
private WebClient webClient(){
final JAXRSClientFactoryBean clientFactoryBean = new JAXRSClientFactoryBean();
clientFactoryBean.setThreadSafe(true);
final WebClient webClient = clientFactoryBean.createWebClient();
final ClientConfiguration config = WebClient.getConfig(webClient);
config.getRequestContext().put(HTTPConduit.NO_IO_EXCEPTIONS, Boolean.TRUE);
// ssl settings
config.getEndpoint().getEndpointInfo().addExtensor(tlsClientParameters);
return webClient;
}
提前感谢您的帮助。
解决方案是使用 CXF 的 HTTPConduitConfigurator,详见此处:https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=49941#ClientHTTPTransport(includingSSLsupport)-HowtouseHTTPConduitConfigurer?
HTTPConduitConfigurer httpConduitConfigurer = new HTTPConduitConfigurer() {
public void configure(String name, String address, HTTPConduit c) {
c.setTlsClientParameters(_tlsParams);
}
}
bus.setExtension(httpConduitConfigurer, HTTPConduitConfigurer.class);
这将在创建的所有管道上设置 TLS 客户端参数,例如使用故障转移时。