You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by cz...@apache.org on 2020/08/24 05:10:52 UTC

[felix-dev] branch master updated: FELIX-5311 Allow Usage of HTTP/2 with Jetty Felix Http Service (#42)

This is an automated email from the ASF dual-hosted git repository.

cziegeler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/felix-dev.git


The following commit(s) were added to refs/heads/master by this push:
     new b668728  FELIX-5311 Allow Usage of HTTP/2 with Jetty Felix Http Service (#42)
b668728 is described below

commit b668728eebaac3883a7874644a92a8bf62d974e7
Author: Eric Norman <er...@gmail.com>
AuthorDate: Sun Aug 23 22:09:12 2020 -0700

    FELIX-5311 Allow Usage of HTTP/2 with Jetty Felix Http Service (#42)
---
 http/jetty/pom.xml                                 | 141 ++++++++++++++++++++-
 .../jetty/internal/ConfigMetaTypeProvider.java     |  35 +++++
 .../felix/http/jetty/internal/JettyConfig.java     |  47 +++++++
 .../felix/http/jetty/internal/JettyService.java    |  23 ++++
 4 files changed, 244 insertions(+), 2 deletions(-)

diff --git a/http/jetty/pom.xml b/http/jetty/pom.xml
index 04477cb..8e317c5 100644
--- a/http/jetty/pom.xml
+++ b/http/jetty/pom.xml
@@ -49,6 +49,98 @@
 
     <build>
         <plugins>
+
+            <!-- Use a groovy script to preserve the META-INF/services/* files for the artifacts that are embeded in the uber jar -->
+            <plugin>
+                <groupId>org.codehaus.gmaven</groupId>
+                <artifactId>groovy-maven-plugin</artifactId>
+                <version>2.1.1</version>
+                <executions>
+                    <execution>
+                        <id>groovy-magic</id>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>execute</goal>
+                        </goals>
+                        <configuration>
+                            <source><![CDATA[
+                                // make an output dir for the merged resource files
+                                def slDir = new File(project.build.directory, "serviceloader-resources");
+                                slDir.mkdirs();
+
+                                // scan each of the artifacts to preserve the information found in any META-INF/services/* files
+                                project.artifacts.each() { artifact ->
+
+                                    if (artifact.getArtifactHandler().isAddedToClasspath() && !org.apache.maven.artifact.Artifact.SCOPE_TEST.equals( artifact.getScope() )) {
+                                        def jar;
+                                        try {
+                                            jar = new java.util.jar.JarFile(artifact.file)
+                                            jar.stream().each() { entry ->
+                                               if (!entry.isDirectory() && entry.name.startsWith("META-INF/services/")) {
+
+                                                   // check if we already have a file with this name
+                                                   def svcFile = new File(slDir, entry.name)
+                                                   def svcSet = new LinkedHashSet();
+                                                   if (svcFile.exists()) {
+                                                       // found existing file, so load the items from the existing file so we can merge
+                                                       svcFile.eachLine { className ->
+                                                           className = className.trim();
+                                                           if (!className.isEmpty()) {
+                                                               svcSet.add(className);
+                                                           }
+                                                       }
+                                                   }
+
+                                                   // read the content of the found entry
+                                                   def lineReader;
+                                                   try {
+                                                       lineReader = new BufferedReader(new InputStreamReader(jar.getInputStream(entry), java.nio.charset.StandardCharsets.UTF_8));
+                                                       def className;
+                                                       while ( ( className = lineReader.readLine() ) != null ) {
+                                                           className = className.trim();
+                                                           if (!className.isEmpty()) {
+                                                               svcSet.add(className);
+                                                           }
+                                                       }
+                                                   } finally {
+                                                       // cleanup
+                                                       if (lineReader != null) {
+                                                           lineReader.close()
+                                                       }
+                                                   }
+
+                                                   // write the merged data to the output file
+                                                   if (!svcSet.isEmpty()) {
+                                                       // make any missing folders
+                                                       svcFile.getParentFile().mkdirs();
+
+                                                       svcFile.withWriter('utf-8') { writer ->
+                                                           svcSet.each() { item ->
+                                                               writer.writeLine item;
+                                                           }
+
+                                                           // finish up with a blank line
+                                                           writer.println();
+                                                       }
+                                                   }
+
+                                               }
+                                            }
+                                        } finally {
+                                            // cleanup
+                                            if (jar != null) {
+                                                jar.close();
+                                            }
+                                        }
+                                    }
+
+                                }
+                            ]]></source>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
             <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
@@ -112,11 +204,19 @@
                             osgi.service;objectClass:List&lt;String&gt;="org.osgi.service.http.runtime.HttpServiceRuntime";
                             uses:="org.osgi.service.http.runtime,org.osgi.service.http.runtime.dto",
                             osgi.service;objectClass:List&lt;String&gt;="org.osgi.service.http.HttpService";
-                            uses:="org.osgi.service.http"
+                            uses:="org.osgi.service.http",
+                            osgi.serviceloader;osgi.serviceloader="org.eclipse.jetty.http.HttpFieldPreEncoder"
                         </Provide-Capability>
                         <Require-Capability>
-                            osgi.contract;filter:="(&amp;(osgi.contract=JavaServlet)(version=3.1))"
+                            osgi.contract;filter:="(&amp;(osgi.contract=JavaServlet)(version=3.1))",
+                            osgi.extender;filter:="(osgi.extender=osgi.serviceloader.registrar)";resolution:=optional,
+                            osgi.extender;filter:="(osgi.extender=osgi.serviceloader.processor)";resolution:=optional,
+                            osgi.serviceloader;filter:="(osgi.serviceloader=org.eclipse.jetty.http.HttpFieldPreEncoder)";resolution:=optional;cardinality:=multiple,
+                            osgi.serviceloader;filter:="(osgi.serviceloader=org.eclipse.jetty.io.ssl.ALPNProcessor$Server)";resolution:=optional;cardinality:=multiple
                         </Require-Capability>
+                        <Include-Resource>
+                            {maven-resources},${project.build.directory}/serviceloader-resources
+                        </Include-Resource>
                         <_removeheaders>
                             Private-Package,Conditional-Package
                         </_removeheaders>
@@ -164,6 +264,23 @@
                                     org.eclipse.jetty.webapp;resolution:=optional,
                                     *
                                 </Import-Package>
+                                <!-- We need to override this from the base configuration to exclude the ServiceLoader capabilities -->
+                                <Provide-Capability>
+                                    osgi.implementation;osgi.implementation="osgi.http";version:Version="1.1";
+                                    uses:="javax.servlet,javax.servlet.http,org.osgi.service.http.context,org.osgi.service.http.whiteboard",
+                                    osgi.service;objectClass:List&lt;String&gt;="org.osgi.service.http.runtime.HttpServiceRuntime";
+                                    uses:="org.osgi.service.http.runtime,org.osgi.service.http.runtime.dto",
+                                    osgi.service;objectClass:List&lt;String&gt;="org.osgi.service.http.HttpService";
+                                    uses:="org.osgi.service.http"
+                                </Provide-Capability>
+                                <!-- We need to override this from the base configuration to exclude the ServiceLoader capabilities -->
+                                <Require-Capability>
+                                    osgi.contract;filter:="(&amp;(osgi.contract=JavaServlet)(version=3.1))"
+                                </Require-Capability>
+                                <!-- We need to override this from the base configuration to exclude the ServiceLoader resources -->
+                                <Include-Resource>
+                                    {maven-resources}
+                                </Include-Resource>
                                 <_removeheaders>
                                     X-Jetty-Version,Private-Package,Conditional-Package 
                                 </_removeheaders>
@@ -249,6 +366,26 @@
             <version>${jetty.version}</version>
         </dependency>
         <dependency>
+            <groupId>org.eclipse.jetty.http2</groupId>
+            <artifactId>http2-server</artifactId>
+            <version>${jetty.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.http2</groupId>
+            <artifactId>http2-common</artifactId>
+            <version>${jetty.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.http2</groupId>
+            <artifactId>http2-hpack</artifactId>
+            <version>${jetty.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-alpn-server</artifactId>
+            <version>${jetty.version}</version>
+        </dependency>
+        <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.service.http</artifactId>
             <version>1.2.1</version>
diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java
index 9e48814..db08887 100644
--- a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java
@@ -432,6 +432,41 @@ class ConfigMetaTypeProvider implements MetaTypeProvider
                 "If not -1, stop timeout for the server in milliseconds.", -1L,
                 bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_STOP_TIMEOUT)));
 
+        adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_HTTP2_ENABLE,
+                "Enable Http/2",
+                "Whether to enable HTTP/2. Default is false.",
+                false,
+                bundle.getBundleContext().getProperty(JettyConfig.FELIX_HTTP2_ENABLE)));
+        adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_HTTP2_MAX_CONCURRENT_STREAMS,
+                "Http/2 Max Concurrent Streams",
+                "The max number of concurrent streams per connection. Default is 128.",
+                128,
+                bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_HTTP2_MAX_CONCURRENT_STREAMS)));
+        adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_HTTP2_INITIAL_STREAM_RECV_WINDOW,
+                "Http/2 Initial Stream Recieve Window",
+                "The initial stream receive window (client to server). Default is 524288.",
+                524288,
+                bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_HTTP2_INITIAL_STREAM_RECV_WINDOW)));
+        adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_HTTP2_INITIAL_SESSION_RECV_WINDOW,
+                "Http/2 Initial Session Recieve Window",
+                "The initial session receive window (client to server). Default is 1048576.",
+                1048576,
+                bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_HTTP2_INITIAL_SESSION_RECV_WINDOW)));
+
+        adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_ALPN_PROTOCOLS,
+                "ALPN Protocols",
+                "The ALPN protocols to consider. Default is h2, http/1.1.",
+                AttributeDefinition.STRING,
+                new String[] {"h2", "http/1.1"},
+                2147483647,
+                null, null,
+                getStringArray(bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_ALPN_PROTOCOLS))));
+        adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_ALPN_DEFAULT_PROTOCOL,
+                "ALPN Default Protocol",
+                "The default protocol when negotiation fails. Default is http/1.1.",
+                "http/1.1",
+                bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_ALPN_DEFAULT_PROTOCOL)));
+
         return new ObjectClassDefinition()
         {
 
diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
index 549c7fe..aa340d7 100644
--- a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
@@ -267,6 +267,24 @@ public final class JettyConfig
     /** Felix specific property to specify the stop timeout of the jetty server */
     public static final String FELIX_JETTY_STOP_TIMEOUT = "org.apache.felix.jetty.stopTimeout";
 
+    /** Felix specific property to control whether to enable HTTP/2. */
+    public static final String FELIX_HTTP2_ENABLE = "org.apache.felix.http2.enable";
+
+    /** Felix specific property to specify the max number of concurrent streams per connection  */
+    public static final String FELIX_JETTY_HTTP2_MAX_CONCURRENT_STREAMS = "org.apache.felix.jetty.http2.maxConcurrentStreams";
+
+    /** Felix specific property to specify the initial stream receive window (client to server)  */
+    public static final String FELIX_JETTY_HTTP2_INITIAL_STREAM_RECV_WINDOW = "org.apache.felix.jetty.http2.initialStreamRecvWindow";
+
+    /** Felix specific property to specify the initial session receive window (client to server)  */
+    public static final String FELIX_JETTY_HTTP2_INITIAL_SESSION_RECV_WINDOW = "org.apache.felix.jetty.http2.initialSessionRecvWindow";
+
+    /** Felix specific property to specify the ALPN protocols to consider  */
+    public static final String FELIX_JETTY_ALPN_PROTOCOLS = "org.apache.felix.jetty.alpn.protocols";
+
+    /** Felix specific property to specify the default protocol when negotiation fails  */
+    public static final String FELIX_JETTY_ALPN_DEFAULT_PROTOCOL = "org.apache.felix.jetty.alpn.defaultProtocol";
+
     private static String validateContextPath(String ctxPath)
     {
         // undefined, empty, or root context path
@@ -530,6 +548,35 @@ public final class JettyConfig
         return useHttps && getHttpsPort() > 0;
     }
 
+    /**
+     * Returns <code>true</code> if HTTP/2 is configured to be used (
+     * {@link #FELIX_HTTP2_ENABLE})
+     */
+    public boolean isUseHttp2()
+    {
+        return getBooleanProperty(FELIX_HTTP2_ENABLE, false);
+    }
+
+    public int getHttp2MaxConcurrentStreams() {
+        return getIntProperty(FELIX_JETTY_HTTP2_MAX_CONCURRENT_STREAMS, 128);
+    }
+
+    public int getHttp2InitialStreamRecvWindow() {
+        return getIntProperty(FELIX_JETTY_HTTP2_INITIAL_STREAM_RECV_WINDOW, 524288);
+    }
+
+    public int getHttp2InitialSessionRecvWindow() {
+        return getIntProperty(FELIX_JETTY_HTTP2_INITIAL_SESSION_RECV_WINDOW, 1048576);
+    }
+
+    public String[] getAlpnProtocols() {
+        return getStringArrayProperty(FELIX_JETTY_ALPN_PROTOCOLS, new String[] {"h2", "http/1.1"} );
+    }
+
+    public String getAlpnDefaultProtocol() {
+        return getProperty(FELIX_JETTY_ALPN_DEFAULT_PROTOCOL, "http/1.1");
+    }
+
     public boolean isProxyLoadBalancerConnection()
     {
         return getBooleanProperty(FELIX_PROXY_LOAD_BALANCER_CONNECTION_ENABLE, false);
diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
index a4b99c8..a067dde 100644
--- a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
@@ -34,7 +34,9 @@ import javax.servlet.SessionTrackingMode;
 import org.apache.felix.http.base.internal.HttpServiceController;
 import org.apache.felix.http.base.internal.logger.SystemLogger;
 import org.apache.felix.http.jetty.internal.webapp.WebAppBundleTracker;
+import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
 import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
 import org.eclipse.jetty.io.ConnectionStatistics;
 import org.eclipse.jetty.security.HashLoginService;
 import org.eclipse.jetty.security.UserStore;
@@ -471,6 +473,27 @@ public final class JettyService extends AbstractLifeCycle.AbstractLifeCycleListe
             httpConfiguration.addCustomizer(customizerWrapper);
         }
 
+        if (this.config.isUseHttp2()) {
+            //add ALPN factory
+            SslConnectionFactory alpnConnFactory = new SslConnectionFactory(sslContextFactory, "alpn");
+            connector.addConnectionFactory(alpnConnFactory);
+
+            ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory(this.config.getAlpnProtocols());
+            alpn.setDefaultProtocol(this.config.getAlpnDefaultProtocol());
+            connector.addConnectionFactory(alpn);
+
+            //Configure a HTTP2 on the ssl connector
+            HTTP2ServerConnectionFactory http2factory = new HTTP2ServerConnectionFactory(httpConfiguration);
+            http2factory.setMaxConcurrentStreams(this.config.getHttp2MaxConcurrentStreams());
+            http2factory.setInitialStreamRecvWindow(this.config.getHttp2InitialStreamRecvWindow());
+            http2factory.setInitialSessionRecvWindow(this.config.getHttp2InitialSessionRecvWindow());
+            connector.addConnectionFactory(http2factory);
+
+            //use http/2 cipher comparator
+            sslContextFactory.setCipherComparator(org.eclipse.jetty.http2.HTTP2Cipher.COMPARATOR);
+            sslContextFactory.setUseCipherSuitesOrder(true);
+        }
+
         configureConnector(connector, this.config.getHttpsPort());
         return startConnector(connector);
     }