Netty 是一個(gè)高性能、異步事件驅(qū)動(dòng)的 NIO 框架,它提供了對(duì) TCP 、 UDP 和文件傳輸?shù)闹С?,作為一個(gè)異步 NIO 框架,Netty 的所有 IO 操作都是異步非阻塞的,通過 Future-Listener 機(jī)制,用戶可以方便的主動(dòng)獲取或者通過通知機(jī)制獲得 IO 操作結(jié)果。
Netty 在保持高性能的同時(shí)又提供了非常易用的 API,實(shí)際使用中只需要實(shí)現(xiàn)自定義的 Handler,那么快速完成開發(fā)后保證 Handler 中數(shù)據(jù)流的正確性就成為了重中之重。
好在 Netty 提供了專門用來測(cè)試 Handler 的類: EmbeddedChannel。通過 EmbeddedChannel 可以測(cè)試 inbound&outbound 兩個(gè)方向的數(shù)據(jù)流來達(dá)到測(cè)試 Handler 的目的。
EmbeddedChannel 提供如下方法:
boolean writeInbound(Object... msgs) // 向 Inbound channel 寫消息
<T> T readInbound() // 從 Inbound channel讀消息
boolean writeOutbound(Object... msgs) // 向 Outbound channel 寫消息
<T> T readOutbound() // 從 Outbound channel 讀消息
boolean finish() // 將 channel 標(biāo)記為 finished
下面就使用以上幾個(gè)方法來完成 Handler 的測(cè)試。
首先將創(chuàng)建以下幾個(gè)簡(jiǎn)單的 Handler:
** NettyHttpRequestHandler(Inbound)**: 接收 Client 的 Request 并作簡(jiǎn)單處理
NettyHttpRelayHandler(Inbound): 將處理過的 Request 包裝成 Response 并轉(zhuǎn)交給下面的 Outbound
NettyHttpResponseHandler(Outbound): 接收 Response 并作簡(jiǎn)單處理
NettyHttpSenderHandler(Outbound): 將 Response 返回給 Client
其中 NettyHttpRelayHandler 和 NettyHttpSenderHandler 無需做單元測(cè)試,一個(gè)僅是將 request 包裝成 response,另一個(gè)僅是將 response 返回給 cilent。
** NettyHttpRequestHandler(Inbound)**
public class NettyHttpRequestHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// update http content to Base64 encode value
FullHttpRequest httpRequest = (FullHttpRequest) msg;
ByteBuf contentBuf = httpRequest.content();
String content = contentBuf.toString(CharsetUtil.UTF_8);
contentBuf.clear();
contentBuf.writeBytes(Base64.getEncoder().encode(content.getBytes(CharsetUtil.UTF_8)));
// update content length
HttpHeaders.setHeader(httpRequest, HttpHeaders.Names.CONTENT_LENGTH, contentBuf.readableBytes());
super.channelRead(ctx, msg);
}
}
NettyHttpRequestHandler 將接收到的 body 內(nèi)容 base64 編碼。
NettyHttpResponseHandler
public class NettyHttpResponseHandler extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
// update http content to Base64 decode value
FullHttpResponse response = (FullHttpResponse) msg;
ByteBuf contentBuf = response.content();
String content = contentBuf.toString(CharsetUtil.UTF_8);
contentBuf.clear();
contentBuf.writeBytes(Base64.getDecoder().decode(content.getBytes(CharsetUtil.UTF_8)));
// update content length
HttpHeaders.setHeader(response, HttpHeaders.Names.CONTENT_LENGTH, contentBuf.readableBytes());
super.write(ctx, msg, promise);
}
}
NettyHttpResponseHandler 將接收到的 body 內(nèi)容 base64 解碼。
開始進(jìn)入到如何對(duì) NettyHttpRequestHandler 和 NettyHttpResponseHandler 進(jìn)行單元測(cè)試的重頭戲。
創(chuàng)建一個(gè) EmbeddedChannel 對(duì)象:
EmbeddedChannel channel = new EmbeddedChannel(
new HttpRequestDecoder(),
new HttpObjectAggregator(10485760),
new NettyHttpResponseHandler(),
new NettyHttpRequestHandler()
);
構(gòu)造 HttpRequest:
FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, POST, "/test");
setRequestHeaders(request);
向 channel 中發(fā)送 request:
HttpContent chunk = new DefaultHttpContent(Unpooled.wrappedBuffer(JSON_DATA.getBytes(UTF_8)));
HttpHeaders.setHeader(request, CONTENT_LENGTH, chunk.content().readableBytes());
// write request to Inbound
channel.writeInbound(request);
// write content to Inbound
channel.writeInbound(chunk);
// write last conent to Inbound
channel.writeInbound(LastHttpContent.EMPTY_LAST_CONTENT);
檢查經(jīng)過 NettyHttpRequestHandler 處理后的數(shù)據(jù):
FullHttpRequest reqMsg = (FullHttpRequest) channel.readInbound();
assertNotNull(reqMsg);
String body = reqMsg.content().toString(UTF_8);
assertNotNull(body);
assertEquals(body, "eyJkYXRhIjogInRoaXMgaXMganNvbiBib2R5In0=");
構(gòu)造 HttpResponse 并發(fā)送到 channel :
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1,
HttpResponseStatus.OK, Unpooled.copiedBuffer(body, UTF_8));
setResponseHeaders(response);
channel.writeOutbound(response);
檢查經(jīng)過 NettyHttpResponseHandler 處理后的數(shù)據(jù):
FullHttpResponse resMsg = (FullHttpResponse) channel.readOutbound();
assertNotNull(resMsg);
body = resMsg.content().toString(UTF_8);
assertNotNull(body);
assertEquals(body, JSON_DATA);
// 將 channel 標(biāo)記為 finished
channel.finish();
以上內(nèi)容展示了 Netty Handler 完整的單元測(cè)試流程,開發(fā)過程中可以編寫充分的單元測(cè)試,盡量保證程序的正確性。
完整版源碼下載地址:netty-handler-tester