Skip to content
Permalink
Browse files
FELIX-5311 Allow Usage of HTTP/2 with Jetty Felix Http Service (#42)
  • Loading branch information
enapps-enorman committed Aug 24, 2020
1 parent 3e7972b commit b668728eebaac3883a7874644a92a8bf62d974e7
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 2 deletions.
@@ -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>
@@ -248,6 +365,26 @@
<artifactId>websocket-server</artifactId>
<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>
@@ -432,6 +432,41 @@ public ObjectClassDefinition getObjectClassDefinition( String id, String locale
"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()
{

@@ -267,6 +267,24 @@
/** 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 boolean isUseHttps()
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);
@@ -34,7 +34,9 @@
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 @@ private boolean initializeHttps()
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);
}

0 comments on commit b668728

Please sign in to comment.