From ac2b14ce187d942fb03a6a2dd11b2dae9aeea7c1 Mon Sep 17 00:00:00 2001 From: Peter Palaga Date: Thu, 31 Aug 2023 23:37:06 +0200 Subject: [PATCH] Introduce quarkus.cxf[.client.myClient].http-conduit-factory as a configurable workaround for #992 CXF clients based on java.net.http.HttpClient leak threads --- .../ROOT/pages/includes/quarkus-cxf.adoc | 36 ++++++++++++++++++ .../cxf/deployment/CxfBuildTimeConfig.java | 13 +++++++ .../cxf/deployment/CxfClientProcessor.java | 37 ++++++++++++++++++- .../io/quarkiverse/cxf/CXFClientInfo.java | 10 +++++ .../java/io/quarkiverse/cxf/CXFRecorder.java | 5 +++ .../io/quarkiverse/cxf/CxfClientConfig.java | 17 +++++++++ .../io/quarkiverse/cxf/CxfClientProducer.java | 17 +++++++++ .../cxf/URLConnectionHTTPConduitFactory.java | 19 ++++++++++ .../src/main/resources/application.properties | 3 ++ 9 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 extensions/core/runtime/src/main/java/io/quarkiverse/cxf/URLConnectionHTTPConduitFactory.java diff --git a/docs/modules/ROOT/pages/includes/quarkus-cxf.adoc b/docs/modules/ROOT/pages/includes/quarkus-cxf.adoc index 4107e6922..589b012d3 100644 --- a/docs/modules/ROOT/pages/includes/quarkus-cxf.adoc +++ b/docs/modules/ROOT/pages/includes/quarkus-cxf.adoc @@ -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]` @@ -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` + |=== \ No newline at end of file diff --git a/extensions/core/deployment/src/main/java/io/quarkiverse/cxf/deployment/CxfBuildTimeConfig.java b/extensions/core/deployment/src/main/java/io/quarkiverse/cxf/deployment/CxfBuildTimeConfig.java index 7c1b8d3d1..eb8985360 100644 --- a/extensions/core/deployment/src/main/java/io/quarkiverse/cxf/deployment/CxfBuildTimeConfig.java +++ b/extensions/core/deployment/src/main/java/io/quarkiverse/cxf/deployment/CxfBuildTimeConfig.java @@ -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; @@ -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 { diff --git a/extensions/core/deployment/src/main/java/io/quarkiverse/cxf/deployment/CxfClientProcessor.java b/extensions/core/deployment/src/main/java/io/quarkiverse/cxf/deployment/CxfClientProcessor.java index 1efa1f092..9026c600e 100644 --- a/extensions/core/deployment/src/main/java/io/quarkiverse/cxf/deployment/CxfClientProcessor.java +++ b/extensions/core/deployment/src/main/java/io/quarkiverse/cxf/deployment/CxfClientProcessor.java @@ -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; @@ -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 #580. * @@ -449,6 +451,39 @@ private void produceUnremovableBean( .forEach(unremovables::produce); } + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + void customizers( + CXFRecorder recorder, + CxfBuildTimeConfig config, + BuildProducer 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( diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFClientInfo.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFClientInfo.java index 91d8f4a9e..5e7ddbd09 100644 --- a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFClientInfo.java +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFClientInfo.java @@ -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 @@ -161,6 +162,8 @@ public class CXFClientInfo { */ private String proxyPassword; + private HTTPConduitImpl httpConduitImpl; + public CXFClientInfo() { } @@ -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) { @@ -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; } @@ -460,4 +465,9 @@ public String getProxyUsername() { public String getProxyPassword() { return proxyPassword; } + + public HTTPConduitImpl getHttpConduitImpl() { + return httpConduitImpl; + } + } diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFRecorder.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFRecorder.java index 5aad53271..6a8b61b0a 100644 --- a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFRecorder.java +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFRecorder.java @@ -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; @@ -153,4 +154,8 @@ public void resetDestinationRegistry(ShutdownContext context) { public void addRuntimeBusCustomizer(RuntimeValue> customizer) { QuarkusBusFactory.addBusCustomizer(customizer.getValue()); } + + public RuntimeValue> setURLConnectionHTTPConduit() { + return new RuntimeValue<>(bus -> bus.setExtension(new URLConnectionHTTPConduitFactory(), HTTPConduitFactory.class)); + } } diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientConfig.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientConfig.java index 14f2516c6..bb814ecfe 100644 --- a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientConfig.java +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientConfig.java @@ -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; @@ -266,4 +267,20 @@ public class CxfClientConfig { @ConfigItem public Optional 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; + } + } diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientProducer.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientProducer.java index 0832056cb..06f6c3ff3 100644 --- a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientProducer.java +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientProducer.java @@ -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; @@ -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; /** @@ -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 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(); diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/URLConnectionHTTPConduitFactory.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/URLConnectionHTTPConduitFactory.java new file mode 100644 index 000000000..dad38eeb0 --- /dev/null +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/URLConnectionHTTPConduitFactory.java @@ -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); + } +} diff --git a/integration-tests/mtom/src/main/resources/application.properties b/integration-tests/mtom/src/main/resources/application.properties index cacbcb6fd..c9a20bcc4 100644 --- a/integration-tests/mtom/src/main/resources/application.properties +++ b/integration-tests/mtom/src/main/resources/application.properties @@ -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