Netty入门2之

在上一章中我们认识了netty,他有三大优点:并发高,传输快,封装好。在这一章我们来用Netty搭建一个HttpServer,从实际开发中了解netty框架的一些特性和概念。

netty.png

认识Http请求

在动手写Netty框架之前,我们先要了解http请求的组成,如下图:

HTTP response component parts

  1. HTTP response 第一部分是包含的头信息
  2. HttpContent 里面包含的是数据,可以后续有多个 HttpContent 部分
  3. LastHttpContent 标记是 HTTP response 的结束,同时可能包含头的尾部信息
  4. 完整的 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,在这里一一介绍:

  1. HttpRequestDecoder,用于解码request
  2. HttpResponseEncoder,用于编码response
  3. aggregator,消息聚合器(重要)。为什么能有FullHttpRequest这个东西,就是因为有他,HttpObjectAggregator,如果没有他,就不会有那个消息是FullHttpRequest的那段Channel,同样也不会有FullHttpResponse。
    如果我们将z'h
    HttpObjectAggregator(512 * 1024)的参数含义是消息合并的数据大小,如此代表聚合的消息内容长度不超过512kb。
  4. 添加我们自己的处理接口

完成启动类之后,接下来就是我们的业务处理类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}

该段代码需要注意的地方如注释所示,有以下四点:

  1. 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的作用就在于此。

  1. 生成response,这里使用的FullHttpResponse,同FullHttpRequest类似,通过这个我们就不用将response拆分成多个channel返回给请求端了。
  2. 添加header描述length。这一步是很重要的一步,如果没有这一步,你会发现用postman发出请求之后就一直在刷新,因为http请求方不知道返回的数据到底有多长。
  3. 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的全部代码和剖析,希望这篇文章能帮到你,有问题评论区沟通。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

java将前端的json数组字符串转换为列表

记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

Netty入门2之 - HelloWorld