Skip to content

HTTP/2 on Jetty: ALPNServerConnectionFactory default protocol set to "h2" #14444

@ath0s

Description

@ath0s

When using an embedded Jetty server, configured for HTTP/2 (using Conscrypt), the ALPNServerConnectionFactory is configured to use "h2" as it's default protocol.
This has the consequence, as I understand it and observe from testing, that when negotiating with a client that does not support ALPN, it will default to using the HTTP/2 protocol.
In my experience, clients that do not support ALPN are unlikely to support HTTP/2, at least the clients that I have tested with.
I suggest that the default protocol should be set to "HTTP/1.1", or be made configurable.

I am using Spring Boot version 2.0.4, with the dependency versions defined in the spring-boot-starter-parent pom

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty.http2</groupId>
            <artifactId>http2-server</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-alpn-conscrypt-server</artifactId>
            <scope>runtime</scope>
        </dependency>

My workaround is to set the default protocol to HTTP/1.1 using a JettyServerCustomizer

 /**
     * Spring Boot configures ALPN with HTTP/2 as default protocol
     * Clients that do not support ALPN are unlikely to support HTTP/2.
     * HTTP/1.1 is a better default.
     *
     * @see org.springframework.boot.web.embedded.jetty.SslServerCustomizer
     */
    @Configuration
    @ConditionalOnClass(NegotiatingServerConnectionFactory.class)
   class JettyAlpnFallbackModifyingWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableJettyWebServerFactory> {

        @Override
        public void customize(ConfigurableJettyWebServerFactory factory) {
            factory.addServerCustomizers((JettyServerCustomizer) server -> Arrays.stream(server.getConnectors())
                    .flatMap(connector -> connector.getConnectionFactories().stream())
                    .flatMap(ofType(NegotiatingServerConnectionFactory.class))
                    .forEach(negotiatingServerConnectionFactory -> negotiatingServerConnectionFactory.setDefaultProtocol(HTTP_1_1.asString())));
        }

        @SuppressWarnings("SameParameterValue")
        private static <F, T> Function<F, Stream<T>> ofType(Class<T> type) {
            return value -> type.isInstance(value) ? Stream.of(type.cast(value)) : Stream.empty();
        }
    }

(note my workaround is not a suggested solution and is probably not generic enough to support all use cases)

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions