Add support for Jetty HTTP/2 clients (#3433)

Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
pull/3446/head
Andrew Fiddian-Green 2023-03-12 09:22:50 +00:00 committed by GitHub
parent a3a95b5bb9
commit a5d65ce2ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 138 additions and 11 deletions

View File

@ -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>

View File

@ -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>

View File

@ -15,6 +15,7 @@ package org.openhab.core.io.net.http;
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;
/**
@ -22,6 +23,7 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
*
* @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 {
@ -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);
@ -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);
@ -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);
}

View File

@ -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;
@ -25,6 +25,10 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
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;
@ -46,6 +50,7 @@ import org.slf4j.LoggerFactory;
* @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
@ -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;
@ -332,7 +340,6 @@ public class WebClientFactoryImpl implements HttpClientFactory, WebSocketFactory
}
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);
@ -362,4 +369,49 @@ public class WebClientFactoryImpl implements HttpClientFactory, WebSocketFactory
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);
}
}
}

View File

@ -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>

View File

@ -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)'

View File

@ -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)',\
@ -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)'

View File

@ -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)'

View File

@ -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)'

View File

@ -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)',\
@ -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)'