在上一章中我们认识了netty,他有三大优点:并发高,传输快,封装好。在这一章我们来用Netty搭建一个HttpServer,从实际开发中了解netty框架的一些特性和概念。
netty.png
认识Http请求
在动手写Netty框架之前,我们先要了解http请求的组成,如下图:
HTTP response component parts
- HTTP response 第一部分是包含的头信息
- HttpContent 里面包含的是数据,可以后续有多个 HttpContent 部分
- LastHttpContent 标记是 HTTP response 的结束,同时可能包含头的尾部信息
- 完整的 HTTP response,由1,2,3组成
从request的介绍我们可以看出来,一次http请求并不是通过一次对话完成的,他中间可能有很次的连接。通过上一章我们队netty的了解,每一次对话都会建立一个channel,并且一个ChannelInboundHandler一般是不会同时去处理多个Channel的。
如何在一个Channel里面处理一次完整的Http请求?这就要用到我们上图提到的FullHttpRequest,我们只需要在使用netty处理channel的时候,只处理消息是FullHttpRequest的Channel,这样我们就能在一个ChannelHandler中处理一个完整的Http请求了。
开始动手
搭建一个Netty服务器,我们只需要两个类——一个是启动类,负责启动(BootStrap)和main方法,一个是ChannelHandler,负责具体的业务逻辑,我们先从启动类说起。
1package com.dz.netty.http; 2 3import io.netty.bootstrap.ServerBootstrap; 4import io.netty.channel.ChannelInitializer; 5import io.netty.channel.ChannelOption; 6import io.netty.channel.nio.NioEventLoopGroup; 7import io.netty.channel.socket.SocketChannel; 8import io.netty.channel.socket.nio.NioServerSocketChannel; 9import io.netty.handler.codec.http.HttpObjectAggregator; 10import io.netty.handler.codec.http.HttpRequestDecoder; 11import io.netty.handler.codec.http.HttpResponseEncoder; 12 13/** 14 * Created by RoyDeng on 17/7/20. 15 */ 16public class HttpServer { 17 18 private final int port; 19 20 public HttpServer(int port) { 21 this.port = port; 22 } 23 24 public static void main(String[] args) throws Exception { 25 if (args.length != 1) { 26 System.err.println( 27 "Usage: " + HttpServer.class.getSimpleName() + 28 " <port>"); 29 return; 30 } 31 int port = Integer.parseInt(args[0]); 32 new HttpServer(port).start(); 33 } 34 35 public void start() throws Exception { 36 ServerBootstrap b = new ServerBootstrap(); 37 NioEventLoopGroup group = new NioEventLoopGroup(); 38 b.group(group) 39 .channel(NioServerSocketChannel.class) 40 .childHandler(new ChannelInitializer<SocketChannel>() { 41 @Override 42 public void initChannel(SocketChannel ch) 43 throws Exception { 44 System.out.println("initChannel ch:" + ch); 45 ch.pipeline() 46 .addLast("decoder", new HttpRequestDecoder()) // 1 47 .addLast("encoder", new HttpResponseEncoder()) // 2 48 .addLast("aggregator", new HttpObjectAggregator(512 * 1024)) // 3 49 .addLast("handler", new HttpHandler()); // 4 50 } 51 }) 52 .option(ChannelOption.SO_BACKLOG, 128) // determining the number of connections queued 53 .childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE); 54 55 b.bind(port).sync(); 56 } 57}
这个类同上一章中出现的Netty简易封装服务器代码类似,不一样的是这里使用了多个ChannelHandler,在这里一一介绍:
- HttpRequestDecoder,用于解码request
- HttpResponseEncoder,用于编码response
- aggregator,消息聚合器(重要)。为什么能有FullHttpRequest这个东西,就是因为有他,HttpObjectAggregator,如果没有他,就不会有那个消息是FullHttpRequest的那段Channel,同样也不会有FullHttpResponse。
如果我们将z'h
HttpObjectAggregator(512 * 1024)的参数含义是消息合并的数据大小,如此代表聚合的消息内容长度不超过512kb。 - 添加我们自己的处理接口
完成启动类之后,接下来就是我们的业务处理类HttpHandler了,先上代码:
1package com.dz.netty.http; 2 3import io.netty.buffer.Unpooled; 4import io.netty.channel.ChannelHandlerContext; 5import io.netty.channel.SimpleChannelInboundHandler; 6import io.netty.handler.codec.http.*; 7import io.netty.util.AsciiString; 8 9/** 10 * Created by RoyDeng on 17/7/20. 11 */ 12public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { // 1 13 14 private AsciiString contentType = HttpHeaderValues.TEXT_PLAIN; 15 16 @Override 17 protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception { 18 System.out.println("class:" + msg.getClass().getName()); 19 DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, 20 HttpResponseStatus.OK, 21 Unpooled.wrappedBuffer("test".getBytes())); // 2 22 23 HttpHeaders heads = response.headers(); 24 heads.add(HttpHeaderNames.CONTENT_TYPE, contentType + "; charset=UTF-8"); 25 heads.add(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes()); // 3 26 heads.add(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); 27 28 ctx.write(response); 29 } 30 31 @Override 32 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 33 System.out.println("channelReadComplete"); 34 super.channelReadComplete(ctx); 35 ctx.flush(); // 4 36 } 37 38 @Override 39 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 40 System.out.println("exceptionCaught"); 41 if(null != cause) cause.printStackTrace(); 42 if(null != ctx) ctx.close(); 43 } 44}
该段代码需要注意的地方如注释所示,有以下四点:
-
Handler需要声明泛型为<FullHttpRequest>,声明之后,只有msg为FullHttpRequest的消息才能进来。
由于泛型的过滤比较简单,我们就不改代码来验证了,但是在这里我们可以利用泛型的特性另外做个小测试,将泛型去掉,并且将HttpServer中.addLast("aggregator", new HttpObjectAggregator(512 * 1024)) // 3这一行代码注释掉,然后观察注释前后的log。
注释前:initChannel ch:[id: 0xcb9d8e9e, L:/0:0:0:0:0:0:0:1:8888 - R:/0:0:0:0:0:0:0:1:58855] class:io.netty.handler.codec.http.HttpObjectAggregator$AggregatedFullHttpRequest channelReadComplete
注释后:
1initChannel ch:[id: 0xc5415409, L:/0:0:0:0:0:0:0:1:8888 - R:/0:0:0:0:0:0:0:1:58567] 2class:io.netty.handler.codec.http.DefaultHttpRequest 3class:io.netty.handler.codec.http.LastHttpContent$1 4channelReadComplete 5channelReadComplete
从中可以看出,如果没有aggregator,那么一个http请求就会通过多个Channel被处理,这对我们的业务开发是不方便的,而aggregator的作用就在于此。
- 生成response,这里使用的FullHttpResponse,同FullHttpRequest类似,通过这个我们就不用将response拆分成多个channel返回给请求端了。
- 添加header描述length。这一步是很重要的一步,如果没有这一步,你会发现用postman发出请求之后就一直在刷新,因为http请求方不知道返回的数据到底有多长。
- channel读取完成之后需要输出缓冲流。如果没有这一步,你会发现postman同样会一直在刷新。
构建HTTPS服务
首先,构建HTTPS服务需要证书,那么什么是SSL证书呢?
SSL 证书就是遵守 SSL协议,由受信任的数字证书颁发机构CA,在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能。
也就是说,HTTPS相比于HTTP服务,能够防止网络劫持,同时具备一定的安全加密作用。
**一般来说,证书可以在阿里云、腾讯云这种云服务上申请。**申请下来之后,证书只能用于指定的域名和服务器上。
netty有提供SSL加密的工具包,只需要通过添加SslHandler,就能快速搭建。基于上面的代码,我们重新定义一个ChannelInitializer。
1public class SSLChannelInitializer extends ChannelInitializer<SocketChannel> { 2 3 private final SslContext sslContext; 4 5 public SSLChannelInitializer() { 6 String keyStoreFilePath = "/root/.ssl/test.pkcs12"; 7 String keyStorePassword = "Password@123"; 8 9 try { 10 KeyStore keyStore = KeyStore.getInstance("PKCS12"); 11 keyStore.load(new FileInputStream(keyStoreFilePath), keyStorePassword.toCharArray()); 12 13 KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 14 keyManagerFactory.init(keyStore, keyStorePassword.toCharArray()); 15 16 sslContext = SslContextBuilder.forServer(keyManagerFactory).build(); 17 } catch (Exception e) { 18 throw new RuntimeException(e); 19 } 20 } 21 22 @Override 23 protected void initChannel(SocketChannel ch) { 24 ChannelPipeline pipeline = ch.pipeline(); 25 SSLEngine sslEngine = sslContext.newEngine(ch.alloc()); 26 pipeline 27 .addLast(new SslHandler(sslEngine)) 28 .addLast("decoder", new HttpRequestDecoder()) 29 .addLast("encoder", new HttpResponseEncoder()) 30 .addLast("aggregator", new HttpObjectAggregator(512 * 1024)) 31 .addLast("handler", new HttpHandler()); 32 ; 33 } 34} 35
以上就是我通过netty做http服务器demo的全部代码和剖析,希望这篇文章能帮到你,有问题评论区沟通。