Netty
Netty は、NIO 実装に基づいた非同期イベント駆動型のウェブアプリケーションフレームワークで、保守性の高い高性能なプロトコルサーバとクライアントを迅速に開発することができます。
Nettyは最も人気のあるNIOフレームワークで、何百もの商用およびビジネスプロジェクトで検証されており、DubboやElasticsearchなど、多くのフレームワークやオープンソースコンポーネントがNettyを基礎RPCとして使用しています。公式サイトよりNettyの特徴をご紹介します:
デザイン
- すべてのトランスポートタイプ(ブロッキングおよびノンブロッキングソケット)の統一API
- 柔軟で拡張可能なイベントモデルに基づく、明確な懸念事項の分離
- 高度にカスタマイズ可能なスレッドモデル - シングルスレッド、1つまたは複数のスレッドプール、SEDAなど
- 真のコネクションレス型データグラムソケットのサポート
使いやすい
- 十分に文書化されたJavadoc、ユーザーガイド、サンプル
- JDK 5 または 6 で十分です。
パフォーマンス
- より高いスループット、より低いレイテンシ
- 資源消費の削減
- 不要なメモリの重複を削減
セキュア
- SSL / TLSおよびStartTLSのフルサポート
これらの特徴を簡単に説明するだけでも十分です。
シンプルなHTTPサーバーの構築
私の方で使用しているネッティのバージョンは
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.48.Final</version>
</dependency>
ブラウザでアクセス先のアドレスを入力してアクセスされたサービスにレスポンスを返す、シンプルなHTTPサーバーを書いてください。
サーバー側のコードは以下の通りです。
@Slf4j
public class Main {
public static void main(String[] args) {
log.info("=========<<<<<<<Nettyを起動する>>>>>>=========");
//つのスレッドグループを構築する。bossGroupは接続を処理し、workGroupはリクエストを処理する。
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workGroup = new NioEventLoopGroup();
try {
//サーバはヘルパークラスを起動する
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
//Channelリクエストを処理するために、ChannelHandlerをセットアップする。.
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//Httpメッセージのエンコーディングを扱う
socketChannel.pipeline().addLast("httpServerCodec", new HttpServerCodec());
//カスタムChannelHandlerはFullRequestを使うので
socketChannel.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
//カスタムChannelHandlerを追加する
socketChannel.pipeline().addLast("httpServerHandler", new NettyServerHandler());
}
});
ChannelFuture sync = b.bind(8888).sync();
log.info("NettyServer開始された:.1:8888");
//サービスのポートが閉じるのを待つ
sync.channel().closeFuture().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
//潔く終了し、スレッドプールを解放する
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
log.info("nettyを終了する");
}
});
} catch (Exception e) {
log.error("NettyServer起動に失敗した", e);
}
}
}
Netty アプリケーションを書く場合、一般的に NioEventLoopGroup bossGroup, workGroup のインスタンスを 2 つ作成します。 まず、この 2 つのインスタンスについて簡単に説明すると、これは実際には 2 つのスレッドプールで、スレッド数はデフォルトで CPU コア数 * 2 になります。bossGroup は接続の処理に使われ、workGroup はリクエストの処理に使われます。
ServerBootstrapは、上で作成した2つのスレッドグループなど、Nettyアプリケーションのスタートアップアセンブリに必要なコンポーネントの一部を設定するために使用されます。 channelメソッドは、サーバー側のリスニングソケットチャンネルNioServerSocketChannelを指定するために使用されます。
.childHandler() メソッドは主に Channel リクエストを処理するための ChannelHandler をセットアップするために使用され、簡単に言えば、Netty サービスへのリクエストが channelhandler の内部で実行されると理解できます。
次に bind メソッドでサービスを 8888 番ポートにバインドしています。 bind メソッドは内部的にポートバインディングなどの一連の動作を行うので、それまでの設定はすべて整っており、ポートバインディングの操作が完了するまで sync メソッドで現在の Thread をブロックしています。次の文では、アプリケーションはサーバーのチャネルがクローズされるまでブロックして待機します。
メインスレッドがブロックせずに解放されるように、cf.channel().closeFuture().addListenerにshutdownGracefully関数を記述します。そして、パイプラインが閉じられるとき、それは自動的に優雅に閉じられます。
new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//Httpメッセージのエンコーディングを扱う
socketChannel.pipeline().addLast("httpServerCodec", new HttpServerCodec());
//カスタムChannelHandlerはFullRequestを使うので
socketChannel.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
//カスタムChannelHandlerを追加する
socketChannel.pipeline().addLast("httpServerHandler", new NettyServerHandler());
}
}
このコードは主に、その後の処理に必要なコーデックと、独自のビジネスを実装するためのカスタムチャンネルハンドラーを追加するためのものです。
Nettyは高性能なネットワーク通信フレームワークですが、低レベルのフレームワークでもあります。NettyにHttpをサポートさせるには、適切なコーデックを提供する必要があります。
Netty独自のHttpコーデックコンポーネントであるHttpServerCodecは、Httpリクエストを扱うときによく使われるHttpRequestDecoderとHttpResponseEncoderを組み合わせたものなので、Nettyはこれらを直接組み合わせて使いやすくしています。Nettyはこれらを直接組み合わせて使いやすくしています。
HttpObjectAggregator はリクエストを FullHttpRequests にマージする機能を提供します。
NettyServerHandler は、自分で実装する必要があるクラスです。
コードは以下の通り:
/**
*
*
* @Version 1.0
* @Description Netty Httpリクエストの設計は、HttpRequestとHttpContentの2つの部分に分かれている。
* HttpRequest 主にリクエストヘッダ、リクエストメソッド、その他の情報を含む
* HttpContent リクエストの本文は以下の情報を含む
* <p>
* Netty 別のクラスFullHttpRequestが提供されている。 FullHttpRequestはリクエストされたすべての情報を含んでいる。
* これはHttpRequestとHttpContentを直接または間接的に継承したインターフェースで、その実装クラスがDefalutFullHttpRequestである。
* つまり、ここではFullHttpRequestを使うことができる。
* FullHttpRequestを使っている場合は、ChannelPipelineにHttpObjectAggregatorのインスタンスを追加する必要があることに注意。
* HttpObjectAggregator はリクエストを一つのFullHttpRequestに変換する。
*/
@Slf4j
public class NettyServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
log.info("リクエストの種類:" + request.method().name());
log.info("uri:" + request.uri());
ByteBuf buf = request.content();
String requestContent = buf.toString(CharsetUtil.UTF_8);
log.info("リクエストボディは:" + requestContent);
//レスポンスを設定する
ByteBuf byteBuf = Unpooled.copiedBuffer("hello world" + requestContent, CharsetUtil.UTF_8);
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf);
//レスポンスヘッダを追加する
response.headers().add(HttpHeaderNames.CONTENT_TYPE, "text/plain");
response.headers().add(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
//クライアントに応答する
ctx.writeAndFlush(response);
}
}
ここまでで、簡単なHTTPサービスが書けました。では、mainメソッドを実行して、postmanでテストしてみましょう。