Создание приложений HTTP (HTTPS) в серии Netty

задняя часть HTTPS HTTP Netty

Оригинальный адрес блога:блог Пимайка

предисловие

В этой статье в основном рассказывается, как создать приложение HTTP/HTTPS с помощью Netty, и используется демонстрация уровня HelloWorld для иллюстрации.

Введение в протокол SSL/TLS

Поскольку мы хотим одновременно создать приложение HTTPS, нам необходимо защитить приложение Netty с помощью SSL/TLS.Вот краткое введение в протокол SSL/TLS.

И SSL, и TLS являются протоколами безопасности транспортного уровня, и история их развития такова:

  • 1995: SSL 2.0, предложенный Netscape, эта версия небезопасна из-за недостатков конструкции, вскоре были обнаружены серьезные уязвимости, и от нее отказались.
  • 1996: SSL 3.0 был написан как RFC и стал популярным В настоящее время (с 2015 года) он небезопасен и должен быть отключен
  • 1999: Организация по стандартизации Интернета TLS1.0 ISOC сменила NetScape и выпустилаОбновленная версия SSL TLS версии 1.0
  • 2006: TLS 1.1 Опубликован как RFC 4346. В основном исправлены уязвимости, связанные с режимом CBC, такие как атака BEAST.
  • 2008: TLS 1.2, опубликованный как RFC 5246. Улучшить безопасность. Текущая (2015 г.) версия, которую следует в основном развертывать, убедитесь, что вы используете именно эту версию.
  • После 2015 года: TLS 1.3, все еще находящийся в разработке, поддерживает 0-rtt, значительно повышает безопасность и отключает методы шифрования, отличные от aead.

Поскольку обе версии SSL сняты с исторической сцены,Вообще говоря, SSL — это TLS.

Схематическая диаграмма протокола безопасности SSL/TLS выглядит следующим образом:

ssl/tls

Протокол SSL/TLS является необязательным уровнем между уровнями HTTP и уровнями TCP.Основные услуги, которые он предоставляет, включают:

  • Аутентифицировать пользователей и серверы, чтобы гарантировать, что данные отправляются на правильный клиент и сервер
  • Шифрование данных для предотвращения кражи данных на полпути
  • Поддерживайте целостность данных и следите за тем, чтобы данные не изменялись при передаче

Для более подробного ознакомления с протоколом SSL/TLS вы можете найти соответствующую информацию, и я не буду здесь вдаваться в подробности.

Пакет JDK javax.net.ssl ​​против OpenSSL/SSLEngine Netty

Для поддержки SSL/TLS Java предоставляет пакет javax.net.ssl, чьи классы SSLContext и SSLEngine делают расшифровку и шифрование довольно простыми и эффективными. SSLContext — это контекст соединения SSL, а SSLEngine в основном используется для исходящих и входящих операций с потоком байтов.

Netty также предоставляет реализацию SSLEngine с использованием набора инструментов OpenSSL, которая имеет лучшую производительность, чем реализация SSLEngine, предоставляемая JDK.

Нетти черезSslHandlerизChannelHandlerРеализовать функции шифрования и дешифрования, гдеSslHandlerВнутренне используйте SSLEngine для выполнения фактической работы, реализация SSLEngine может быть JDK.SSLEngine, также может быть НеттиOpenSslEngine, разумеется, рекомендуется использовать Netty's OpenSslEngine, так как он имеет лучшую производительность.Процесс расшифровки и шифрования через SslHandler показан на следующем рисунке (отрывок из "Netty In Action"):

sslhandler

В большинстве случаев SslHandler будет первым ChannelHandler в ChannelPipeline. Это гарантирует, что шифрование произойдет только после того, как все другие ChannelHandler применят свою логику к данным.

Компоненты HTTP-запроса и ответа

HTTP основан на модели запрос/ответ: клиент отправляет HTTP-запрос на сервер, а сервер возвращает HTTP-ответ.Netty предоставляет различные кодировщики и декодеры для упрощения использования этого протокола.

Часть HTTP-запроса выглядит следующим образом:

httpRequest

Компоненты ответа HTTP следующие:

httpreponse

Как показано на двух рисунках выше, HTTP-запрос/ответ может состоять из нескольких частей данных и всегда заканчивается частью LastHttpContent.FullHttpRequestа такжеFullHttpResponseСообщения — это специальные подтипы, которые представляют полные запросы и ответы соответственно.

Реализованы все типы HTTP-сообщенийHttpObjectинтерфейс

HTTP-декодеры, кодировщики и кодеки

Netty предоставляет кодировщики и декодеры для HTTP-сообщений:

  • HttpRequestEncoder: Кодировщик, для клиента, отправить запрос на сервер
  • HttpResponseEecoder: кодировщик, используемый на стороне сервера, отправляет ответ клиенту
  • HttpRequestDecoder: Декодер, используемый на стороне сервера, получает запросы от клиентов
  • HttpResponseDecoder: Декодер, используемый клиентом для получения запросов от сервера.

кодек:

  • HttpClientCodec: кодек для использования на стороне клиента, эквивалентныйHttpRequestEncoderа такжеHttpResponseDecoderКомбинация
  • HttpServerCodec: кодек для сервера, эквивалентныйHttpRequsetDecoderа такжеHttpResponseEncoderКомбинация

кHttpServerCodecНапример, его схема структуры наследования классов выглядит следующим образом:

httpservercodec

HttpServerCodec также реализуетChannelInboundHandlerа такжеChannelOutboundHandlerинтерфейс для достижения возможности кодировать и декодировать в то же время.

Агрегатор:

  • HttpObjectAggregator: Агрегатор, который может объединять несколько частей сообщения вFullHttpRequestилиFullHttpResponseИнформация. Причина использования этого агрегатора заключается в том, что декодер HTTP генерирует несколько объектов сообщения в каждом сообщении HTTP, напримерHttpRequest/HttpResponse,HttpContent,LastHttpContent, Используйте агрегатор, чтобы объединить их в полное содержимое сообщения, чтобы вам не приходилось заботиться о фрагментации сообщения.

код приложения

Исходный код для создания HTTP/HTTPS-приложения на основе Netty взят из демо-версии, предоставленной официальным Netty.Я внес некоторые изменения.Исходный адрес: https://github.com/netty/netty/tree/4.1/example /src/main/java/io/netty/example/http/helloworld

Исходный код:

public class HttpHelloWorldServer {


    static final boolean SSL = System.getProperty("ssl") != null;
    static final int PORT = Integer.parseInt(System.getProperty("port", SSL ? "8443" : "8080"));

    public static void main(String[] args) throws Exception {
        final SslContext sslContext;
        //判断SSL是否为true,为true表示使用HTTPS连接,反之,使用HTTP
        if (SSL) {
            //使用Netty自带的证书工具生成一个数字证书
            SelfSignedCertificate certificate = new SelfSignedCertificate();
            sslContext = SslContextBuilder.forServer(certificate.certificate(), certificate.privateKey()).build();
        } else {
            sslContext = null;
        }
        EventLoopGroup boss = new NioEventLoopGroup(1);
        EventLoopGroup worker = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(boss, worker)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            if (sslContext != null) {
                                pipeline.addLast(sslContext.newHandler(ch.alloc()));
                            }
                            //添加一个HTTP的编解码器
                            pipeline.addLast(new HttpServerCodec());
                            //添加HTTP消息聚合器
                            pipeline.addLast(new HttpObjectAggregator(64 * 1024));
                            //添加一个自定义服务端Handler
                            pipeline.addLast(new HttpHelloWorldServerHandler());
                        }
                    });
            ChannelFuture future = bootstrap.bind(PORT).sync();
            System.err.println("Open your web browser and navigate to " +
                    (SSL? "https" : "http") + "://127.0.0.1:" + PORT + '/');

            future.channel().closeFuture().sync();
        } finally {
            boss.shutdownGracefully().sync();
            worker.shutdownGracefully().sync();
        }

    }

}

Интерпретация кода

Сначала определите, существует ли системный атрибут ssl. Если он существует, это означает, что используется безопасное соединение, в противном случае используется обычное HTTP-соединение.

  final SslContext sslContext;
        if (SSL) {
            SelfSignedCertificate certificate = new SelfSignedCertificate();
            sslContext = SslContextBuilder.forServer(certificate.certificate(), certificate.privateKey()).build();
        } else {
            sslContext = null;
        }

Как показано в приведенном выше коде, когда SSL имеет значение true, используйте инструмент сертификата подписи, который поставляется с Netty, чтобы настроить цифровой сертификат, отправляемый сервером клиенту.

Следующие шаги такие же, как и общие шаги программы сервера Netty, сначала создайтеServerBootstrapКлассы запуска, настройки и привязкиNioEventLoopGroupПул потоков, создайте канал на стороне сервера, добавьте ChannelHandler. Стоит отметить, что все добавленные обработчики каналов являются обработчиками, связанными с HTTP.

HttpHelloWorldServerHandler

Пользовательский код обработчика выглядит следующим образом:

public class HttpHelloWorldServerHandler extends SimpleChannelInboundHandler<HttpObject> {
    private static final AsciiString CONTENT_TYPE = AsciiString.cached("Content-Type");
    private static final AsciiString CONTENT_LENGTH = AsciiString.cached("Content-Length");
    private static final AsciiString CONNECTION = AsciiString.cached("Connection");
    private static final AsciiString KEEP_ALIVE = AsciiString.cached("keep-alive");
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        if (msg instanceof HttpRequest) {
            HttpRequest req = (HttpRequest) msg;
            System.out.println("浏览器请求方式:"+req.method().name());
            String content = "";
            if ("/hello".equals(req.uri())) {
                content = "hello world";
                response2Client(ctx,req,content);
            } else {
                content = "Connect the Server";
                response2Client(ctx,req,content);
            }
        }
    }

    private void response2Client(ChannelHandlerContext ctx, HttpRequest req, String content) {
        boolean keepAlive = HttpUtil.isKeepAlive(req);
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(content.getBytes()));
        response.headers().set(CONTENT_TYPE, "text/plain");
        response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes());
        if (!keepAlive) {
            ctx.write(response).addListener(ChannelFutureListener.CLOSE);
        } else {
            response.headers().set(CONNECTION, KEEP_ALIVE);
            ctx.write(response);
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

Входящий поток данных обрабатывается в этом обработчике, но этот код просто обрабатываетGETпросьба, нетPOSTзапрос обрабатывается, поэтому, когда браузер отправляетGETПо запросу этот обработчик определяет тело ответа HTTP.FullHttpResponse, установите некоторые заголовки ответа, такие как ·Content-type,Connection,Content-Lengthи т. д., установите содержимое ответа, затем передайтеctx.writeспособ написать HTTP-сообщение

AsciiString

При настройке заголовков ответа мы использовалиAsciiString, начиная с Netty 4.1, обеспечивает реализациюCharSequenceинтерфейсAsciiString, что касаетсяCharSequenceто естьStringродительский класс.AsciiStringВключенные символы занимают всего 1 байт, что экономит место при работе со строками US-ASCII или ISO-8859-1. Например, кодек HTTP используетAsciiStringимя заголовка обработки, потому чтоAsciiStringкодировать вByteBufНе будет затрат на преобразование типов, и используется его внутренняя реализацияbyte, а дляStringДругими словами, внутреннийchar[], чтобы использовать String, вам нужно преобразовать char в byte, поэтомуAsciiStringЛучшая производительность, чем тип String.

контрольная работа

Тест клиента:

curl

Журнал сервера:

server

резюме

Выше показано, как создать простое приложение HTTP/HTTPS с помощью Netty. Конечно, вышеприведенная программа относится к демо-версии, предоставленной Netty официально.Netty также предоставляет множество других примеров, что неплохо для вводного обучения.Подробный адрес: https://github.com/netty/netty/tree/ 4.1 /пример/src/main/java/io/netty/пример

Ссылки и благодарности