Серия Netty: разрешите TLS поддерживать http2

Java HTTP Netty

Мало знаний, большой вызов! Эта статья участвует в "Необходимые знания для программистов«Творческая деятельность

Эта статья приняла участие"Проект "Звезда раскопок"", чтобы выиграть творческий подарочный пакет и бросить вызов творческим поощрительным деньгам.

«Добро пожаловать для обсуждения в области комментариев, официальный представитель NuggetsПроект «Звезда раскопок»После мероприятия в комментариях будет разыграно 100 штук Наггетсов.Подробнее о лотерее читайте в статье о мероприятии».

Введение

Мы знаем, что хотя протокол HTTP2 не требует использования HTTPS, для большинства браузеров, если вы хотите использовать HTTP2, вы должны использовать HTTPS, поэтому нам нужно понять, как поддерживать http2 в TLS netty.

Протоколы расширения TLS NPN и ALPN

Протокол HTTP2 разработан на основе протокола spdy.И spdy, и http2 разработали расширения протокола TLS для работы в среде HTTPS.

Они называются NPN (согласование следующего протокола) и ALPN (согласование протокола прикладного уровня).

Они определяют протокол для обмена данными приложения между клиентом и сервером после рукопожатия протокола TLS. Среди них ALPN может перечислить протоколы данных прикладного уровня, поддерживаемые клиентом, когда клиент устанавливает связь с сервером в первый раз.

Итак, какие протоколы поддерживаются spdy и http2 соответственно?

Netty предоставляет класс ApplicationProtocolNames, в котором определены соответствующие протоколы, где ALPN соответствует http2 и http1.1, а sydy соответствует spdy/1, spdy/2, spdy/3:


    /**
     * HTTP version 2
     */
    public static final String HTTP_2 = "h2";

    /**
     * {@code "http/1.1"}: HTTP version 1.1
     */
    public static final String HTTP_1_1 = "http/1.1";

    /**
     * {@code "spdy/3.1"}: SPDY version 3.1
     */
    public static final String SPDY_3_1 = "spdy/3.1";

    /**
     * {@code "spdy/3"}: SPDY version 3
     */
    public static final String SPDY_3 = "spdy/3";

    /**
     * {@code "spdy/2"}: SPDY version 2
     */
    public static final String SPDY_2 = "spdy/2";

    /**
     * {@code "spdy/1"}: SPDY version 1
     */
    public static final String SPDY_1 = "spdy/1";

SslProvider

В настоящее время существует две реализации SSL в netty, одна — JDK, а другая — OPENSSL.Разные реализации по-разному поддерживают расширения протокола TLS. Он предоставляет метод isAlpnSupported для определения того, поддерживается ли ALPN в соответствии с разницей во входящем провайдере.

    public static boolean isAlpnSupported(final SslProvider provider) {
        switch (provider) {
            case JDK:
                return JdkAlpnApplicationProtocolNegotiator.isAlpnSupported();
            case OPENSSL:
            case OPENSSL_REFCNT:
                return OpenSsl.isAlpnSupported();
            default:
                throw new Error("Unknown SslProvider: " + provider);
        }
    }

Если вы используете JDK8, вы можете получить следующее сообщение об ошибке после запуска:

ALPN is only supported in Java9 or if you use conscrypt as your provider or have the jetty alpn stuff on the class path.

То есть, если JDK используется в качестве поставщика SSL по умолчанию, он не поддерживает ALPN. Необходимо обновить до java9.

Согласно подсказке, если вы добавите conscrypt в путь к классам:

        <dependency>
            <groupId>org.conscrypt</groupId>
            <artifactId>conscrypt-openjdk-uber</artifactId>
            <version>2.5.2</version>
        </dependency>

После запуска он получит следующую ошибку:

Unable to wrap SSLEngine of type 'sun.security.ssl.SSLEngineImpl'

Как это сделать? Ответ заключается в использовании Open SSL, и вам нужно добавить:

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-tcnative-boringssl-static</artifactId>
            <version>2.0.40.Final</version>
        </dependency>

Проверено и выполнено безупречно.

ApplicationProtocolConfig

ApplicationProtocolConfig — это класс конфигурации протокола, который netty предоставляет для передачи в SSLEngine.Он в основном имеет четыре свойства:

    private final List<String> supportedProtocols;
    private final Protocol protocol;
    private final SelectorFailureBehavior selectorBehavior;
    private final SelectedListenerFailureBehavior selectedBehavior;

поддерживаемые протоколы — это поддерживаемые протоколы передачи данных, такие как HTTP2 выше, HTTP1.1 или spdy/1, spdy/2, spdy/3 и т. д. выше.

протокол является расширением TLS, например ALPN или NPN.

selectorBehavior — способ поведения при выборе протокола, есть 3 способа:

FATAL_ALERT: если совпадение для узла, выбирающего протокол приложения, не найдено, рукопожатие завершится ошибкой. NO_ADVERTISE: если узел, который выбирает протокол приложения, не находит соответствия, он будет делать вид, что не поддерживает расширения TLS в рукопожатии. CHOOSE_MY_LAST_PROTOCOL: если для узла, выбирающего протокол приложения, совпадений не найдено, будет использоваться последний предложенный протокол.

selectedBehavior — это способ поведения после уведомления выбранного протокола, и есть 3 способа:

ПРИНЯТЬ: Если узел не поддерживает протокол приложения, выбранный другим узлом, узел не поддерживает расширение TLS по умолчанию, а затем продолжает рукопожатие. FATAL_ALERT: если узел не поддерживает протокол приложения, выбранный равноправным узлом, рукопожатие завершается неудачно. CHOOSE_MY_LAST_PROTOCOL: если узел не поддерживает протокол приложения, выбранный одноранговым узлом, будет использоваться последний рекомендованный протокол.

Создайте SslContext

С помощью провайдера ApplicationProtocolConfig вы можете создать SslContext. Сначала создайте провайдера SSL:

 SslProvider provider =  SslProvider.isAlpnSupported(SslProvider.OPENSSL)  ? SslProvider.OPENSSL : SslProvider.JDK;
           

По умолчанию в качестве поставщика ssl используется JDK или OpenSSL, если вы используете OpenSSL.

Мы используем SslContextBuilder.forServer для создания SslContext, этот метод должен передавать сертификат и privateKey, для простоты мы используем самоподписанный SelfSignedCertificate:

 SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();

Вы также можете установить для него такую ​​информацию, как sslProvider, шифры и applicationProtocolConfig:

sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
                .sslProvider(provider)
                //支持的cipher
                .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
                .applicationProtocolConfig(new ApplicationProtocolConfig(
                    Protocol.ALPN,
                    // 目前 OpenSsl 和 JDK providers只支持NO_ADVERTISE
                    SelectorFailureBehavior.NO_ADVERTISE,
                    // 目前 OpenSsl 和 JDK providers只支持ACCEPT
                    SelectedListenerFailureBehavior.ACCEPT,
                    ApplicationProtocolNames.HTTP_2,
                    ApplicationProtocolNames.HTTP_1_1))
                .build();

ProtocolNegotiationHandler

Наконец, нам нужно выполнить различную обработку в соответствии с различными протоколами, используемыми при согласовании. Netty предоставляет ApplicationProtocolNegotiationHandler.Если вы его настроите, вам нужно будет наследовать только этот класс.Например, мы обрабатываем запросы HTTP1 и HTTP2 отдельно в соответствии с именем протокола:

   public class MyNegotiationHandler extends ApplicationProtocolNegotiationHandler {
       public MyNegotiationHandler() {
           super(ApplicationProtocolNames.HTTP_1_1);
       }
  
       protected void configurePipeline(ChannelHandlerContext ctx, String protocol) {
           if (ApplicationProtocolNames.HTTP_2.equals(protocol) {
               configureHttp2(ctx);
           } else if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) {
               configureHttp1(ctx);
           } else {
               throw new IllegalStateException("unknown protocol: " + protocol);
           }
       }
   }

Затем добавьте его в ChannelPipeline:

   public class MyInitializer extends ChannelInitializer<Channel> {
       private final SslContext sslCtx;
  
       public MyInitializer(SslContext sslCtx) {
           this.sslCtx = sslCtx;
       }
  
       protected void initChannel(Channel ch) {
           ChannelPipeline p = ch.pipeline();
           p.addLast(sslCtx.newHandler(...)); // Adds SslHandler
           p.addLast(new MyNegotiationHandler());
       }
   }

Суммировать

Выше приведен полный процесс настройки TLS для поддержки HTTP2 в netty.

Примеры этой статьи могут относиться к:learn-netty4

Эта статья была включена вwoohoo.floydpress.com/26-Netty-color…

Самая популярная интерпретация, самая глубокая галантерея, самые краткие уроки и множество трюков, о которых вы не знаете, ждут вас!

Добро пожаловать, чтобы обратить внимание на мой официальный аккаунт: «Программируйте эти вещи», разбирайтесь в технологиях, лучше поймите себя!