Оригинальный адрес блога:блог Пимайка
предисловие
В этой статье в основном рассказывается, как создать приложение 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 является необязательным уровнем между уровнями 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 будет первым ChannelHandler в ChannelPipeline. Это гарантирует, что шифрование произойдет только после того, как все другие ChannelHandler применят свою логику к данным.
Компоненты HTTP-запроса и ответа
HTTP основан на модели запрос/ответ: клиент отправляет HTTP-запрос на сервер, а сервер возвращает HTTP-ответ.Netty предоставляет различные кодировщики и декодеры для упрощения использования этого протокола.
Часть HTTP-запроса выглядит следующим образом:
Компоненты ответа HTTP следующие:
Как показано на двух рисунках выше, HTTP-запрос/ответ может состоять из нескольких частей данных и всегда заканчивается частью LastHttpContent.FullHttpRequest
а такжеFullHttpResponse
Сообщения — это специальные подтипы, которые представляют полные запросы и ответы соответственно.
Реализованы все типы HTTP-сообщенийHttpObject
интерфейс
HTTP-декодеры, кодировщики и кодеки
Netty предоставляет кодировщики и декодеры для HTTP-сообщений:
-
HttpRequestEncoder
: Кодировщик, для клиента, отправить запрос на сервер -
HttpResponseEecoder
: кодировщик, используемый на стороне сервера, отправляет ответ клиенту -
HttpRequestDecoder
: Декодер, используемый на стороне сервера, получает запросы от клиентов -
HttpResponseDecoder
: Декодер, используемый клиентом для получения запросов от сервера.
кодек:
-
HttpClientCodec
: кодек для использования на стороне клиента, эквивалентныйHttpRequestEncoder
а такжеHttpResponseDecoder
Комбинация -
HttpServerCodec
: кодек для сервера, эквивалентныйHttpRequsetDecoder
а такжеHttpResponseEncoder
Комбинация
к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.
контрольная работа
Тест клиента:
Журнал сервера:
резюме
Выше показано, как создать простое приложение HTTP/HTTPS с помощью Netty. Конечно, вышеприведенная программа относится к демо-версии, предоставленной Netty официально.Netty также предоставляет множество других примеров, что неплохо для вводного обучения.Подробный адрес: https://github.com/netty/netty/tree/ 4.1 /пример/src/main/java/io/netty/пример