Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Jetty HTTP/2 clients #3433

Merged
merged 11 commits into from Mar 12, 2023
8 changes: 8 additions & 0 deletions bom/compile/pom.xml
Expand Up @@ -176,6 +176,14 @@
<scope>compile</scope>
</dependency>

<!-- Jetty HTTP2 -->
<dependency>
<groupId>org.eclipse.jetty.http2</groupId>
<artifactId>http2-client</artifactId>
<version>${jetty.version}</version>
<scope>compile</scope>
</dependency>

<!-- JmDNS -->
<dependency>
<groupId>org.jmdns</groupId>
Expand Down
14 changes: 14 additions & 0 deletions bom/runtime/pom.xml
Expand Up @@ -750,6 +750,20 @@
<scope>compile</scope>
</dependency>

<!-- Jetty HTTP2 -->
<dependency>
<groupId>org.eclipse.jetty.http2</groupId>
<artifactId>http2-client</artifactId>
<version>${jetty.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-alpn-java-client</artifactId>
<version>${jetty.version}</version>
<scope>compile</scope>
</dependency>

<!-- Xbean -->
<dependency>
<groupId>org.apache.xbean</groupId>
Expand Down
Expand Up @@ -15,13 +15,15 @@
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.util.ssl.SslContextFactory;

/**
* Factory class to create Jetty http clients
*
* @author Michael Bock - Initial contribution
* @author Martin van Wingerden - add createHttpClient without endpoint
* @author Andrew Fiddian-Green - Added support for HTTP2 client creation
*/
@NonNullByDefault
public interface HttpClientFactory {
Expand All @@ -36,7 +38,6 @@ public interface HttpClientFactory {
* @param consumerName the for identifying the consumer in the Jetty thread pool.
* Must be between 4 and 20 characters long and must contain only the following characters [a-zA-Z0-9-_]
* @return the Jetty client
* @throws NullPointerException if {@code consumerName} is {@code null}
* @throws IllegalArgumentException if {@code consumerName} is invalid
*/
HttpClient createHttpClient(String consumerName);
Expand All @@ -52,7 +53,6 @@ public interface HttpClientFactory {
* Must be between 4 and 20 characters long and must contain only the following characters [a-zA-Z0-9-_]
* @param sslContextFactory the SSL factory managing TLS encryption
* @return the Jetty client
* @throws NullPointerException if {@code consumerName} is {@code null}
* @throws IllegalArgumentException if {@code consumerName} is invalid
*/
HttpClient createHttpClient(String consumerName, @Nullable SslContextFactory sslContextFactory);
Expand All @@ -64,4 +64,33 @@ public interface HttpClientFactory {
* @return the shared Jetty http client
*/
HttpClient getCommonHttpClient();

/**
* Creates a new Jetty HTTP/2 client.
* The returned client is not started yet. You have to start it yourself before using.
* Don't forget to stop a started client again after its usage.
* The client lifecycle should be the same as for your service.
* DO NOT CREATE NEW CLIENTS FOR EACH REQUEST!
*
* @param consumerName for identifying the consumer in the Jetty thread pool.
* Must be between 4 and 20 characters long and must contain only the following characters [a-zA-Z0-9-_]
* @return the Jetty HTTP/2 client
* @throws IllegalArgumentException if {@code consumerName} is invalid
*/
HTTP2Client createHttp2Client(String consumerName);

/**
* Creates a new Jetty HTTP/2 client.
* The returned client is not started yet. You have to start it yourself before using.
* Don't forget to stop a started client again after its usage.
* The client lifecycle should be the same as for your service.
* DO NOT CREATE NEW CLIENTS FOR EACH REQUEST!
*
* @param consumerName for identifying the consumer in the Jetty thread pool.
* Must be between 4 and 20 characters long and must contain only the following characters [a-zA-Z0-9-_]
* @param sslContextFactory the SSL factory managing TLS encryption
* @return the Jetty HTTP/2 client
* @throws IllegalArgumentException if {@code consumerName} is invalid
*/
HTTP2Client createHttp2Client(String consumerName, @Nullable SslContextFactory sslContextFactory);
}
Expand Up @@ -12,10 +12,10 @@
*/
package org.openhab.core.io.net.http.internal;

import java.nio.ByteBuffer;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;

import javax.net.ssl.SSLContext;
Expand All @@ -25,6 +25,10 @@
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpProxy;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.websocket.client.WebSocketClient;
Expand All @@ -46,6 +50,7 @@
* @author Michael Bock - Initial contribution
* @author Kai Kreuzer - added web socket support
* @author Martin van Wingerden - Add support for ESHTrustManager
* @author Andrew Fiddian-Green - Added support for HTTP2 client creation
*/
@Component(immediate = true, configurationPid = "org.openhab.webclient")
@NonNullByDefault
Expand Down Expand Up @@ -77,6 +82,9 @@ public class WebClientFactoryImpl implements HttpClientFactory, WebSocketFactory
private int maxThreadsCustom;
private int keepAliveTimeoutCustom; // in s

private boolean hpackLoadTestDone = false;
private @Nullable HttpClientInitializationException hpackException = null;

@Activate
public WebClientFactoryImpl(final @Reference ExtensibleTrustManager extensibleTrustManager) {
this.extensibleTrustManager = extensibleTrustManager;
Expand Down Expand Up @@ -332,7 +340,6 @@ private QueuedThreadPool createThreadPool(String consumerName, int minThreads, i
}

private void checkConsumerName(String consumerName) {
Objects.requireNonNull(consumerName, "consumerName must not be null");
if (consumerName.length() < MIN_CONSUMER_NAME_LENGTH) {
throw new IllegalArgumentException(
"consumerName " + consumerName + " too short, minimum " + MIN_CONSUMER_NAME_LENGTH);
Expand Down Expand Up @@ -362,4 +369,49 @@ private SslContextFactory createSslContextFactory() {

return sslContextFactory;
}

@Override
public HTTP2Client createHttp2Client(String consumerName) {
return createHttp2Client(consumerName, null);
}

@Override
public HTTP2Client createHttp2Client(String consumerName, @Nullable SslContextFactory sslContextFactory) {
logger.debug("http client for consumer {} requested", consumerName);
checkConsumerName(consumerName);
return createHttp2ClientInternal(consumerName, sslContextFactory);
}

private HTTP2Client createHttp2ClientInternal(String consumerName, @Nullable SslContextFactory sslContextFactory) {
try {
logger.debug("creating HTTP/2 client for consumer {}", consumerName);

if (!hpackLoadTestDone) {
try {
PreEncodedHttpField field = new PreEncodedHttpField(HttpHeader.C_METHOD, "PUT");
ByteBuffer bytes = ByteBuffer.allocate(32);
field.putTo(bytes, HttpVersion.HTTP_2);
hpackException = null;
} catch (Exception e) {
hpackException = new HttpClientInitializationException("Jetty HTTP/2 hpack module not loaded", e);
}
hpackLoadTestDone = true;
}
if (hpackException != null) {
throw hpackException;
}

HTTP2Client http2Client = new HTTP2Client();
http2Client.addBean(sslContextFactory != null ? sslContextFactory : createSslContextFactory());
http2Client.setExecutor(
createThreadPool(consumerName, minThreadsCustom, maxThreadsCustom, keepAliveTimeoutCustom));

return http2Client;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new HttpClientInitializationException(
"unexpected checked exception during initialization of the Jetty HTTP/2 client", e);
}
}
}
4 changes: 4 additions & 0 deletions features/karaf/openhab-tp/src/main/feature/feature.xml
Expand Up @@ -71,12 +71,16 @@

<feature name="openhab.tp-httpclient" version="${project.version}">
<capability>openhab.tp;feature=httpclient;version=${jetty.version}</capability>
<feature dependency="true">pax-web-jetty-http2</feature>
<feature dependency="true">pax-web-jetty-http2-jdk9</feature>
<bundle dependency="true">mvn:javax.servlet/javax.servlet-api/3.1.0</bundle>
<bundle dependency="true">mvn:org.eclipse.jetty/jetty-alpn-client/${jetty.version}</bundle>
<bundle dependency="true">mvn:org.eclipse.jetty/jetty-client/${jetty.version}</bundle>
<bundle dependency="true">mvn:org.eclipse.jetty/jetty-http/${jetty.version}</bundle>
<bundle dependency="true">mvn:org.eclipse.jetty/jetty-util/${jetty.version}</bundle>
<bundle dependency="true">mvn:org.eclipse.jetty/jetty-io/${jetty.version}</bundle>
<bundle dependency="true">mvn:org.eclipse.jetty/jetty-proxy/${jetty.version}</bundle>
<bundle dependency="true">mvn:org.eclipse.jetty.http2/http2-client/${jetty.version}</bundle>
<bundle dependency="true">mvn:org.eclipse.jetty.websocket/websocket-api/${jetty.version}</bundle>
<bundle dependency="true">mvn:org.eclipse.jetty.websocket/websocket-common/${jetty.version}</bundle>
<bundle dependency="true">mvn:org.eclipse.jetty.websocket/websocket-client/${jetty.version}</bundle>
Expand Down
Expand Up @@ -65,4 +65,8 @@ Fragment-Host: org.openhab.core.auth.oauth2client
org.eclipse.jetty.websocket.common;version='[9.4.50,9.4.51)',\
org.ops4j.pax.logging.pax-logging-api;version='[2.2.0,2.2.1)',\
org.osgi.service.component;version='[1.5.0,1.5.1)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)'
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
org.eclipse.jetty.alpn.client;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.http2.client;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.http2.common;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.http2.hpack;version='[9.4.50,9.4.51)'
8 changes: 6 additions & 2 deletions itests/org.openhab.core.model.item.tests/itest.bndrun
Expand Up @@ -88,7 +88,6 @@ Fragment-Host: org.openhab.core.model.item
com.sun.jna;version='[5.12.1,5.12.2)',\
xstream;version='[1.4.19,1.4.20)',\
org.apache.felix.configadmin;version='[1.9.26,1.9.27)',\
org.apache.felix.http.servlet-api;version='[1.2.0,1.2.1)',\
org.apache.felix.scr;version='[2.2.4,2.2.5)',\
org.eclipse.jetty.client;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.http;version='[9.4.50,9.4.51)',\
Expand All @@ -110,4 +109,9 @@ Fragment-Host: org.openhab.core.model.item
org.ops4j.pax.web.pax-web-spi;version='[8.0.15,8.0.16)',\
org.ops4j.pax.web.pax-web-tomcat-common;version='[8.0.15,8.0.16)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
org.osgi.service.component;version='[1.5.0,1.5.1)'
org.osgi.service.component;version='[1.5.0,1.5.1)',\
org.apache.felix.http.servlet-api;version='[1.1.2,1.1.3)',\
org.eclipse.jetty.alpn.client;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.http2.client;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.http2.common;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.http2.hpack;version='[9.4.50,9.4.51)'
6 changes: 5 additions & 1 deletion itests/org.openhab.core.model.rule.tests/itest.bndrun
Expand Up @@ -114,4 +114,8 @@ Fragment-Host: org.openhab.core.model.rule.runtime
org.ops4j.pax.web.pax-web-spi;version='[8.0.15,8.0.16)',\
org.ops4j.pax.web.pax-web-tomcat-common;version='[8.0.15,8.0.16)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
org.osgi.service.component;version='[1.5.0,1.5.1)'
org.osgi.service.component;version='[1.5.0,1.5.1)',\
org.eclipse.jetty.alpn.client;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.http2.client;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.http2.common;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.http2.hpack;version='[9.4.50,9.4.51)'
6 changes: 5 additions & 1 deletion itests/org.openhab.core.model.script.tests/itest.bndrun
Expand Up @@ -111,4 +111,8 @@ Fragment-Host: org.openhab.core.model.script
org.ops4j.pax.web.pax-web-runtime;version='[8.0.15,8.0.16)',\
org.ops4j.pax.web.pax-web-spi;version='[8.0.15,8.0.16)',\
org.ops4j.pax.web.pax-web-tomcat-common;version='[8.0.15,8.0.16)',\
org.osgi.service.component;version='[1.5.0,1.5.1)'
org.osgi.service.component;version='[1.5.0,1.5.1)',\
org.eclipse.jetty.alpn.client;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.http2.client;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.http2.common;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.http2.hpack;version='[9.4.50,9.4.51)'
8 changes: 6 additions & 2 deletions itests/org.openhab.core.model.thing.tests/itest.bndrun
Expand Up @@ -97,7 +97,6 @@ Fragment-Host: org.openhab.core.model.thing
io.methvin.directory-watcher;version='[0.17.1,0.17.2)',\
com.sun.jna;version='[5.12.1,5.12.2)',\
org.apache.felix.configadmin;version='[1.9.26,1.9.27)',\
org.apache.felix.http.servlet-api;version='[1.2.0,1.2.1)',\
org.apache.felix.scr;version='[2.2.4,2.2.5)',\
org.eclipse.jetty.client;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.http;version='[9.4.50,9.4.51)',\
Expand All @@ -119,4 +118,9 @@ Fragment-Host: org.openhab.core.model.thing
org.ops4j.pax.web.pax-web-spi;version='[8.0.15,8.0.16)',\
org.ops4j.pax.web.pax-web-tomcat-common;version='[8.0.15,8.0.16)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
org.osgi.service.component;version='[1.5.0,1.5.1)'
org.osgi.service.component;version='[1.5.0,1.5.1)',\
org.apache.felix.http.servlet-api;version='[1.1.2,1.1.3)',\
org.eclipse.jetty.alpn.client;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.http2.client;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.http2.common;version='[9.4.50,9.4.51)',\
org.eclipse.jetty.http2.hpack;version='[9.4.50,9.4.51)'