Netty4(十):实现 HttpServer 服务器 、Htt
2018-03-22 本文已影响76人
聪明的奇瑞
认识 Http 请求与响应
- 在编写代码之前先了解完整的 Http 请求的组成:
- 首先包含头信息 HTTP Request
- 然后包含多个数据 HttpContent
- 结束的标记是 LastHttpContent
- 完整的 Http 响应的组成:
- 首先包含头信息 HTTP response
- 然后包含多个数据 HttpContent
- 结束的标记是 LastHttpContent
- 注意:HTTP 请求和响应由许多部分组成,需要提供一个聚合器合并消息到 FullHttpRequest 和 FullHttpResponse
实现 HttpServer 响应请求
目标:当用浏览器请求 localhost:8080 端口时,HttpServer 响应该请求并返回字符串 test
- 首先编写 Handler,这里有几个要注意的:
- 该 Handler 继承自 SimpleChannelInboundHandler 用于处理指定 FullHttpRequest 类型的消息
- 通过 FullHttpResponse 来响应请求
- 添加响应头 header 的 length 属性,否则浏览器请求之后一直在刷新
- channel 读取完成之后需要输出缓冲流,否则浏览器请求之后一直在刷新
public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
private AsciiString contentType = HttpHeaderValues.TEXT_PLAIN;
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
System.out.println("class:" + msg.getClass().getName());
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
Unpooled.wrappedBuffer("test".getBytes()));
HttpHeaders heads = response.headers();
heads.add(HttpHeaderNames.CONTENT_TYPE, contentType + "; charset=UTF-8");
heads.add(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
heads.add(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
ctx.write(response);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
super.channelReadComplete(ctx);
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (null != cause) cause.printStackTrace();
if (null != ctx) ctx.close();
}
}
- 编写 ChannelInitializer 用于配置 Http 请求连接的 Channel,这里的几个 Handler 作用如下:
- HttpRequestDecoder:用于解码 Http Request
- HttpResponseEncoder:用于编码 Http Response
- HttpObjectAggregator:消息聚合器,把多个消息转换为一个单一的 FullHttpRequest 或 FullHttpResponse。其构造器参数含义是消息合并的数据大小,如此代表聚合的消息内容长度不超过 512kb
public class HttpInitializer extends ChannelInitializer {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("decoder", new HttpRequestDecoder())
.addLast("encoder", new HttpResponseEncoder())
.addLast("aggregator", new HttpObjectAggregator(512 * 1024))
.addLast("handler", new HttpHandler());
}
}
- 编写服务器
public class HttpServer {
private final int port;
public HttpServer(int port) {
this.port = port;
}
public static void main(String[] args) throws Exception {
int port = (args.length == 1) ? Integer.parseInt(args[0]) : 8080;
new HttpServer(port).start();
}
public void start() throws Exception {
NioEventLoopGroup group = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group(group)
.channel(NioServerSocketChannel.class)
.childHandler(new HttpInitializer())
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE);
ChannelFuture f = b.bind(port).sync(); // 绑定端口,调用 ChannelFuture 的 sync() 阻塞方法等待绑定完成
// 调用 closeFuture() 方法返回此通道关闭时的 ChannelFuture
// 调用 ChannelFuture 的 sync() 阻塞方法直到服务端关闭链路之后才退出 main() 函数
f.channel().closeFuture().sync();
}
}
HTTPS
- Java 提供了 SslContext 和 SslEngine 可以实现 SSL 加密解密,而 Netty 只需要添加 SslHandler 就能快速实现 SSL,它内部是通过 SSLEngine 来进行加密解密 ,它的流程如下:
- 加密的入站数据被 SslHandler 拦截,并被解密
- 普通数据传过 SslHandler
- SslHandler 加密数据并将它传递出站
public class SSLChannelInitializer extends ChannelInitializer<SocketChannel> {
private final SslContext sslContext;
public SSLChannelInitializer() {
String keyStoreFilePath = "/root/.ssl/test.pkcs12";
String keyStorePassword = "Password@123";
try {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream(keyStoreFilePath), keyStorePassword.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
sslContext = SslContextBuilder.forServer(keyManagerFactory).build();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
SSLEngine sslEngine = sslContext.newEngine(ch.alloc());
pipeline
.addLast(new SslHandler(sslEngine))
.addLast("decoder", new HttpRequestDecoder())
.addLast("encoder", new HttpResponseEncoder())
.addLast("aggregator", new HttpObjectAggregator(512 * 1024))
.addLast("handler", new HttpHandler());
;
}
}
内容压缩
- 使用 HTTP 时建议压缩数据以减少传输流量,Netty 支持“gzip” 和 “deflate”,并且提供了两个 ChannelHandler 用于压缩和解压
- 客户端显示支持加密模式
GET /encrypted-area HTTP/1.1
Host: www.example.com
Accept-Encoding: gzip, deflate
- 服务器端对内容进行压缩
public class HttpAggregatorInitializer extends ChannelInitializer<Channel> {
private final boolean isClient;
public HttpAggregatorInitializer(boolean isClient) {
this.isClient = isClient;
}
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
if (isClient) {
pipeline.addLast("codec", new HttpClientCodec());
pipeline.addLast("decompressor",new HttpContentDecompressor());
} else {
pipeline.addLast("codec", new HttpServerCodec());
pipeline.addLast("compressor",new HttpContentCompressor());
}
}
}