Skip to content

Commit

Permalink
Introduce quarkus.cxf[.client.myClient].http-conduit-factory as a
Browse files Browse the repository at this point in the history
configurable workaround for #992 CXF clients based on
java.net.http.HttpClient leak threads
  • Loading branch information
ppalaga committed Sep 5, 2023
1 parent 272256f commit 41e8bea
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 1 deletion.
36 changes: 36 additions & 0 deletions docs/modules/ROOT/pages/includes/quarkus-cxf.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,24 @@ endif::add-copy-button-to-env-var[]
|`%CLASSES_DIR%/wsdl/%SIMPLE_CLASS_NAME%.wsdl`


a|icon:lock[title=Fixed at build time] [[quarkus-cxf_quarkus.cxf.http-conduit-factory]]`link:#quarkus-cxf_quarkus.cxf.http-conduit-factory[quarkus.cxf.http-conduit-factory]`


[.description]
--
If not set or set to `DefaultHTTPConduitFactory`, the selection of `HTTPConduitFactory` implementation will be left to CXF. If set to `URLConnectionHTTPConduitFactory`, the `HTTPConduitFactory` for the CXF Bus will be set to an implementation returning `org.apache.cxf.transport.http.URLConnectionHTTPConduit` - this is equivalent to setting `org.apache.cxf.transport.http.forceURLConnection` system property to `true` in CXF 4.0.3{plus}. Using the `URLConnectionHTTPConduitFactory` value in combination with `io.quarkiverse.cxf:quarkus-cxf-rt-transports-http-hc5` causes a build time error.

ifdef::add-copy-button-to-env-var[]
Environment variable: env_var_with_copy_button:+++QUARKUS_CXF_HTTP_CONDUIT_FACTORY+++[]
endif::add-copy-button-to-env-var[]
ifndef::add-copy-button-to-env-var[]
Environment variable: `+++QUARKUS_CXF_HTTP_CONDUIT_FACTORY+++`
endif::add-copy-button-to-env-var[]
-- a|
`DefaultHTTPConduitFactory`, `URLConnectionHTTPConduitFactory`
|`default-http-conduit-factory`


a|icon:lock[title=Fixed at build time] [[quarkus-cxf_quarkus.cxf.codegen.wsdl2java.-named-parameter-sets-.includes]]`link:#quarkus-cxf_quarkus.cxf.codegen.wsdl2java.-named-parameter-sets-.includes[quarkus.cxf.codegen.wsdl2java."named-parameter-sets".includes]`


Expand Down Expand Up @@ -1329,4 +1347,22 @@ endif::add-copy-button-to-env-var[]
--|string
|


a| [[quarkus-cxf_quarkus.cxf.client.-clients-.http-conduit-factory]]`link:#quarkus-cxf_quarkus.cxf.client.-clients-.http-conduit-factory[quarkus.cxf.client."clients".http-conduit-factory]`


[.description]
--
If not set or set to `DefaultHTTPConduitFactory`, the selection of `HTTPConduitFactory` implementation will be left to CXF; if set to `URLConnectionHTTPConduitFactory`, the `HTTPConduitFactory` for this client will be set to an implementation returning `org.apache.cxf.transport.http.URLConnectionHTTPConduit`.

ifdef::add-copy-button-to-env-var[]
Environment variable: env_var_with_copy_button:+++QUARKUS_CXF_CLIENT__CLIENTS__HTTP_CONDUIT_FACTORY+++[]
endif::add-copy-button-to-env-var[]
ifndef::add-copy-button-to-env-var[]
Environment variable: `+++QUARKUS_CXF_CLIENT__CLIENTS__HTTP_CONDUIT_FACTORY+++`
endif::add-copy-button-to-env-var[]
-- a|
`DefaultHTTPConduitFactory`, `URLConnectionHTTPConduitFactory`
|`default-http-conduit-factory`

|===
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.Map;
import java.util.Optional;

import io.quarkiverse.cxf.CxfClientConfig.HTTPConduitImpl;
import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigPhase;
Expand Down Expand Up @@ -43,6 +44,18 @@ public class CxfBuildTimeConfig {
@ConfigItem(name = "java2ws")
public Java2WsConfig java2ws;

/**
* If not set or set to {@code DefaultHTTPConduitFactory}, the selection of {@code HTTPConduitFactory} implementation will
* be left to CXF.
* If set to {@code URLConnectionHTTPConduitFactory}, the {@code HTTPConduitFactory} for the CXF Bus will be set to an
* implementation returning {@code org.apache.cxf.transport.http.URLConnectionHTTPConduit} - this is equivalent to
* setting {@code org.apache.cxf.transport.http.forceURLConnection} system property to {@code true} in CXF 4.0.3+.
* Using the {@code URLConnectionHTTPConduitFactory} value in combination with
* {@code io.quarkiverse.cxf:quarkus-cxf-rt-transports-http-hc5} causes a build time error.
*/
@ConfigItem(defaultValue = "DefaultHTTPConduitFactory")
public HTTPConduitImpl httpConduitFactory;

@ConfigGroup
public static class CodeGenConfig {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import io.quarkiverse.cxf.CXFClientData;
import io.quarkiverse.cxf.CXFClientInfo;
import io.quarkiverse.cxf.CXFRecorder;
import io.quarkiverse.cxf.CxfClientConfig.HTTPConduitImpl;
import io.quarkiverse.cxf.CxfClientProducer;
import io.quarkiverse.cxf.CxfFixedConfig;
import io.quarkiverse.cxf.CxfFixedConfig.ClientFixedConfig;
Expand Down Expand Up @@ -285,7 +286,8 @@ void generateClientProducers(
* to the user application. Why we have do that: First, the interface is package-visible so that adding it to
* the client proxy definition forces GraalVM to generate the proxy class in
* {@value CxfClientProducer#RUNTIME_INITIALIZED_PROXY_MARKER_INTERFACE_PACKAGE} package rather than under a random
* package/class name. Thanks to that we can request the postponed initialization of the generated proxy class by package
* package/class name. Thanks to that we can request the postponed initialization of the generated proxy class by
* package
* name.
* More details in <a href="https://github.com/quarkiverse/quarkus-cxf/issues/580">#580</a>.
*
Expand Down Expand Up @@ -449,6 +451,39 @@ private void produceUnremovableBean(
.forEach(unremovables::produce);
}

@BuildStep
@Record(ExecutionTime.STATIC_INIT)
void customizers(
CXFRecorder recorder,
CxfBuildTimeConfig config,
BuildProducer<RuntimeBusCustomizerBuildItem> customizers) {
switch (config.httpConduitFactory) {
case DefaultHTTPConduitFactory:
// nothing to do
break;
case URLConnectionHTTPConduitFactory:
try {
Class.forName(
"io.quarkiverse.cxf.transport.http.hc5.QuarkusAsyncHTTPConduitFactory.QuarkusAsyncHTTPConduitFactory");
/*
* This is bad: the user has to choose whether he wants URLConnectionHTTPConduitFactory or
* QuarkusAsyncHTTPConduitFactory
*/
throw new RuntimeException(
"Cannot use quarkus.cxf.http-conduit-impl=URLConnectionHTTPConduitFactory and io.quarkiverse.cxf:quarkus-cxf-rt-transports-http-hc5 simultaneously."
+ " Either remove quarkus.cxf.http-conduit-impl=URLConnectionHTTPConduitFactory from application.properties"
+ " or remove the io.quarkiverse.cxf:quarkus-cxf-rt-transports-http-hc5 dependency");
} catch (ClassNotFoundException e) {
/* Fine, we can set the URLConnectionHTTPConduitFactory */
customizers.produce(new RuntimeBusCustomizerBuildItem(recorder.setURLConnectionHTTPConduit()));
}
break;
default:
throw new IllegalStateException("Unexpected " + HTTPConduitImpl.class.getSimpleName() + " value: "
+ config.httpConduitFactory);
}
}

private static class ProxyInfo {

public static ProxyInfo of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.apache.cxf.transports.http.configuration.ConnectionType;
import org.apache.cxf.transports.http.configuration.ProxyServerType;

import io.quarkiverse.cxf.CxfClientConfig.HTTPConduitImpl;
import io.quarkus.arc.Unremovable;

@Unremovable
Expand Down Expand Up @@ -161,6 +162,8 @@ public class CXFClientInfo {
*/
private String proxyPassword;

private HTTPConduitImpl httpConduitImpl;

public CXFClientInfo() {
}

Expand Down Expand Up @@ -222,6 +225,7 @@ public CXFClientInfo(CXFClientInfo other) {
this.proxyServerType = other.proxyServerType;
this.proxyUsername = other.proxyUsername;
this.proxyPassword = other.proxyPassword;
this.httpConduitImpl = other.httpConduitImpl;
}

public CXFClientInfo withConfig(CxfClientConfig config) {
Expand Down Expand Up @@ -260,6 +264,7 @@ public CXFClientInfo withConfig(CxfClientConfig config) {
this.proxyServerType = config.proxyServerType;
this.proxyUsername = config.proxyUsername.orElse(null);
this.proxyPassword = config.proxyPassword.orElse(null);
this.httpConduitImpl = config.httpConduitFactory;
return this;
}

Expand Down Expand Up @@ -460,4 +465,9 @@ public String getProxyUsername() {
public String getProxyPassword() {
return proxyPassword;
}

public HTTPConduitImpl getHttpConduitImpl() {
return httpConduitImpl;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.function.Consumer;

import org.apache.cxf.Bus;
import org.apache.cxf.transport.http.HTTPConduitFactory;
import org.jboss.logging.Logger;

import io.quarkiverse.cxf.devconsole.DevCxfServerInfosSupplier;
Expand Down Expand Up @@ -153,4 +154,8 @@ public void resetDestinationRegistry(ShutdownContext context) {
public void addRuntimeBusCustomizer(RuntimeValue<Consumer<Bus>> customizer) {
QuarkusBusFactory.addBusCustomizer(customizer.getValue());
}

public RuntimeValue<Consumer<Bus>> setURLConnectionHTTPConduit() {
return new RuntimeValue<>(bus -> bus.setExtension(new URLConnectionHTTPConduitFactory(), HTTPConduitFactory.class));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.apache.cxf.transports.http.configuration.ConnectionType;
import org.apache.cxf.transports.http.configuration.ProxyServerType;

import io.quarkus.runtime.annotations.ConfigDocEnumValue;
import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConvertWith;
Expand Down Expand Up @@ -266,4 +267,20 @@ public class CxfClientConfig {
@ConfigItem
public Optional<String> proxyPassword;

/**
* If not set or set to {@code DefaultHTTPConduitFactory}, the selection of {@code HTTPConduitFactory} implementation will
* be left to CXF;
* if set to {@code URLConnectionHTTPConduitFactory}, the {@code HTTPConduitFactory} for this client will be set to an
* implementation returning {@code org.apache.cxf.transport.http.URLConnectionHTTPConduit}.
*/
@ConfigItem(defaultValue = "DefaultHTTPConduitFactory")
public HTTPConduitImpl httpConduitFactory;

public enum HTTPConduitImpl {
@ConfigDocEnumValue("DefaultHTTPConduitFactory")
DefaultHTTPConduitFactory,
@ConfigDocEnumValue("URLConnectionHTTPConduitFactory")
URLConnectionHTTPConduitFactory;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static java.util.stream.Collectors.toList;

import java.io.Closeable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
Expand All @@ -22,9 +23,11 @@
import org.apache.cxf.interceptor.Interceptor;
import org.apache.cxf.message.Message;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transport.http.HTTPConduitFactory;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import org.jboss.logging.Logger;

import io.quarkiverse.cxf.CxfClientConfig.HTTPConduitImpl;
import io.quarkiverse.cxf.annotation.CXFClient;

/**
Expand Down Expand Up @@ -128,6 +131,20 @@ private Object produceCxfClient(CXFClientInfo cxfClientInfo) {
addToCols(inFaultInterceptor, factory.getInFaultInterceptors());
}

switch (cxfClientInfo.getHttpConduitImpl()) {
case DefaultHTTPConduitFactory:
// nothing to do
break;
case URLConnectionHTTPConduitFactory:
final Map<String, Object> props = new HashMap<>();
props.put(HTTPConduitFactory.class.getName(), new URLConnectionHTTPConduitFactory());
factory.setProperties(props);
break;
default:
throw new IllegalStateException("Unexpected " + HTTPConduitImpl.class.getSimpleName() + " value: "
+ cxfClientInfo.getHttpConduitImpl());
}

LOGGER.debug("cxf client loaded for " + cxfClientInfo.getSei());
Object result = factory.create();
final HTTPConduit httpConduit = (HTTPConduit) ClientProxy.getClient(result).getConduit();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.quarkiverse.cxf;

import java.io.IOException;

import org.apache.cxf.Bus;
import org.apache.cxf.service.model.EndpointInfo;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transport.http.HTTPConduitFactory;
import org.apache.cxf.transport.http.HTTPTransportFactory;
import org.apache.cxf.transport.http.URLConnectionHTTPConduit;
import org.apache.cxf.ws.addressing.EndpointReferenceType;

public class URLConnectionHTTPConduitFactory implements HTTPConduitFactory {
@Override
public HTTPConduit createConduit(HTTPTransportFactory f, Bus bus, EndpointInfo endpointInfo, EndpointReferenceType target)
throws IOException {
return new URLConnectionHTTPConduit(bus, endpointInfo, target);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ quarkus.cxf.endpoint."/mtom".implementor = io.quarkiverse.cxf.it.ws.mtom.server.
quarkus.cxf.endpoint."/mtom".handlers = io.quarkiverse.cxf.it.ws.mtom.server.MtomEnforcer

quarkus.native.resources.includes = *.properties,*.jks,*.wsdl,*.xml

# Workaround for https://github.com/quarkiverse/quarkus-cxf/issues/992
quarkus.cxf.http-conduit-factory = URLConnectionHTTPConduitFactory
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
package io.quarkiverse.cxf.it.ws.mtom.server;

import org.apache.cxf.BusFactory;
import org.apache.cxf.transport.http.HTTPConduitFactory;

import io.quarkiverse.cxf.URLConnectionHTTPConduitFactory;
import io.quarkus.test.junit.QuarkusIntegrationTest;

@QuarkusIntegrationTest
public class MtomIT extends MtomTest {

public MtomIT() {
/*
* quarkus.cxf.http-conduit-factory = URLConnectionHTTPConduitFactory is not effective for the JVM
* running the native tests. Thus we have to set the ConduitFactory manually.
*/
BusFactory.getDefaultBus().setExtension(new URLConnectionHTTPConduitFactory(), HTTPConduitFactory.class);
}
}

0 comments on commit 41e8bea

Please sign in to comment.