Netty应用:快速了解http各版本的特性 HttpServer的小demo

HTTP协议

0.9版本

GET /index.html

服务端只能返回html格式,传输过程只能处理文字。

1.0版本

支持任何格式的内容,包括图像、视频、二进制等等

引入了POST命令、HEAD命令

HEAD命令 : 这个命令和 get 是有相似之处的 只返回头部信息,不会返回全部内容,速度较快,一般用来验证连接的有效性

增加了请求头、状态码,以及权限、缓存等

那么1.0 他的会是什么样子的呢?

请求

GET / HTTP/1.0
User-Agent:Mozilla/1.0
Accept: */*

响应格式

HTTP/1.0 200 OK
Content-Type: text/plain
Content-Encoding: gzip

<html>
  <body> hello world </body>
</html>

a、 Content-Type

服务端通知客户端,当前数据的格式

示例: text/html 、 image/png 、 application/pdf 、 video/mp4

前面是一级类型,后面是二级类型,用斜杠分隔; 还可以增加其他参数,如编码格式。

Content-Type: text/plain; charset=utf-8

b、Content-Encoding

表示数据压缩的方式,gzip、compress、deflate

对应客户端的字段为 Accept-Encoding,代表接收哪些压缩方式

c、缺点和问题

每个TCP连接只能发送一个请求,发送完毕连接关闭,使用成本很高,性能较差。

Connection: keep-alive   - 非标准字段

1.1版本

http1.0推出后的半年 1.1版本也随之而来,

这个版本也一直沿用至今,当前我们大部分的http请求 都是用的 1.1 版本的

这里我们使用 postman 来请求一下 常用的搜索引擎 cn.bing.com

请求

GET / HTTP/1.1
User-Agent: PostmanRuntime/7.28.4
Accept: */*
Postman-Token: 2e1942a8-9e7c-4c89-87d7-a46c11205b8c
Host: cn.bing.com
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

响应

 
HTTP/1.1 200 OK
Cache-Control: private
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
Content-Encoding: br
Vary: Accept-Encoding
P3P: CP="NON UNI COM NAV STA LOC CURa DEVa PSAa PSDa OUR IND"
Set-Cookie: SUID=M; domain=.bing.com; expires=Sun, 21-Nov-2021 09:49:46 GMT; path=/; HttpOnly
Set-Cookie: MUID=015C0F598C4C6F831C8A1FAC8D066E50; domain=.bing.com; expires=Thu, 15-Dec-2022 09:49:46 GMT; path=/
Set-Cookie: MUIDB=015C0F598C4C6F831C8A1FAC8D066E50; expires=Thu, 15-Dec-2022 09:49:46 GMT; path=/; HttpOnly
Set-Cookie: _EDGE_S=F=1&SID=1793E6C6781468071F6CF633795E69AE; domain=.bing.com; path=/; HttpOnly
Set-Cookie: _EDGE_V=1; domain=.bing.com; expires=Thu, 15-Dec-2022 09:49:46 GMT; path=/; HttpOnly
Set-Cookie: SRCHD=AF=NOFORM; domain=.bing.com; expires=Mon, 20-Nov-2023 09:49:46 GMT; path=/
Set-Cookie: SRCHUID=V=2&GUID=A3776BC1B7E749699ADB5F7804C9FEE3&dmnchg=1; domain=.bing.com; expires=Mon, 20-Nov-2023 09:49:46 GMT; path=/
Set-Cookie: SRCHUSR=DOB=20211120; domain=.bing.com; expires=Mon, 20-Nov-2023 09:49:46 GMT; path=/
Set-Cookie: SRCHHPGUSR=SRCHLANG=zh-Hans; domain=.bing.com; expires=Mon, 20-Nov-2023 09:49:46 GMT; path=/
Set-Cookie: _SS=SID=1793E6C6781468071F6CF633795E69AE; domain=.bing.com; path=/
Set-Cookie: ULC=; domain=.bing.com; expires=Fri, 19-Nov-2021 09:49:46 GMT; path=/
Set-Cookie: _HPVN=CS=eyJQbiI6eyJDbiI6MSwiU3QiOjAsIlFzIjowLCJQcm9kIjoiUCJ9LCJTYyI6eyJDbiI6MSwiU3QiOjAsIlFzIjowLCJQcm9kIjoiSCJ9LCJReiI6eyJDbiI6MSwiU3QiOjAsIlFzIjowLCJQcm9kIjoiVCJ9LCJBcCI6dHJ1ZSwiTXV0ZSI6dHJ1ZSwiTGFkIjoiMjAyMS0xMS0yMFQwMDowMDowMFoiLCJJb3RkIjowLCJHd2IiOjAsIkRmdCI6bnVsbCwiTXZzIjowLCJGbHQiOjAsIkltcCI6MX0=; domain=.bing.com; expires=Mon, 20-Nov-2023 09:49:46 GMT; path=/
X-SNR-Routing: 1
X-Cache: CONFIG_NOCACHE
X-MSEdge-Ref: Ref A: C567D165BB114CD98893F50FEF5FAEE8 Ref B: BJ1EDGE0716 Ref C: 2021-11-20T09:49:46Z
Date: Sat, 20 Nov 2021 09:49:45 GMT
 
The console only showsresponse bodies smaller than 10 KB inline. To view the complete body, inspect it by clicking  Open.
  • 持久连接,含义为默认不关闭tcp连接,可以被多个请求复用。大多时候,浏览器对同一个域名,允许同时建立6个连接。
  • 管道机制,支持客户端发送多个请求,管理请求的顺序的。服务器还是按照接受请求的顺序,返回对应的响应结果。
  • Content-Length, 用来区分数据包的重要字段
  • 支持PUT、DELETE、PATCH等命令

缺点和问题

当部分请求耗时较长时,仍会阻塞后续请求的处理速度,这种现象叫做“队头阻塞”/“线头阻塞”。

2.0版本

解决队头阻塞的问题,使用的是多路复用的方式。

多路复用 不只是在 netty上有 他可以运用到很多地方

说了这么多 上代码来操作一下吧!!

我们的编写思路是这样的

  • 编写初始化服务端
  • 编写自定义初始化器 和 自定义处理器
  • 启动postman 查看我们设置的 http 的响应结果

我们这里有三个类

  • HttpServer 初始化服务端
  • MyHttpHandler 自定义处理器
  • MyHttpInitializer 自定义初始化

首先是 server

我们需要在初始化服务端的时候 设置主从线程模型(Netty中常用)
设置 启动参数 和阻塞队列的长度等设置
设置 初始化

public class HttpServer {
    public static void main(String[] args) {
        //可以自定义线程的数量
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        // 默认创建的线程数量 = CPU 处理器数量 *2
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .handler(new LoggingHandler())
                //当前连接被阻塞的时候,BACKLOG代表的事 阻塞队列的长度
                .option(ChannelOption.SO_BACKLOG, 128)
                //设置连接保持为活动状态
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childHandler(new MyHttpInitializer());

        try {
        //设置为异步启动 异步 关闭
            ChannelFuture future = serverBootstrap.bind(9988).sync();
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
       		//netty的优雅关闭 指 等一切执行完毕之后 慢慢的关闭
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

编写 初始化

继承ChannelInitializer泛型为Channel,用来进行设置出站解码器和入站编码器
使用 codec netty封装好的解码器,这样我们就不用每次定义 解码和编码

public class MyHttpInitializer extends ChannelInitializer<Channel> {
    @Override
    protected void initChannel(Channel channel) throws Exception {
        ChannelPipeline pipeline = channel.pipeline();
        //先对应请求解码,后对响应解码
        //pipeline.addLast("decoder", new HttpRequestDecoder());
        //pipeline.addLast("encoder", new HttpRequestEncoder());

        //当然每次我们都要解码编码很麻烦,netty也有为我们提供对应的解决方案,建议直接使用这个 不会出错
        pipeline.addLast("codec", new HttpServerCodec());
        //压缩数据
        pipeline.addLast("compressor", new HttpContentCompressor());
        //聚合完整的信息 参数代表可以处理的最大值 此时的是 512 kb
        pipeline.addLast("aggregator", new HttpObjectAggregator(512 * 1024));
        pipeline.addLast(new MyHttpHandler());
    }
}

有了初始化,我们还需要一个做事的 那就是 处理器 Handler

netty帮我们封装了返回完整http响应的类 DefaultFullHttpResponse
我们只需要在读取的时候 设置协议,状态码和响应信息,
配置响应头的类型和长度 就可以完成对请求的响应

/*
 * 泛型需要设置为 FullHttpRequest
 * 筛选 message 为此类型的消息才处理
 * */
public class MyHttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws Exception {
        //DefaultFullHttpResponse 是一个默认的完整的http响应
        DefaultFullHttpResponse response = new DefaultFullHttpResponse(
                HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
                Unpooled.wrappedBuffer("hello http netty demo".getBytes())
        );
        //    我们还需要设置响应头
        // 设置请求 响应头字段 可以使用 HttpHeaderNmaes
        // 设置字段时 可以使用
        HttpHeaders headers = response.headers();
        headers.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN + ";charset=UTF-8");
        headers.add(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
        ctx.write(response);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }
}

启动结果
Netty应用:快速了解http各版本的特性 HttpServer的小demo
postman 差看到的请求响应 参数
可以看到我们设置的 http1.1 协议
类型 text/plain 长度 47
响应给客户端的 内容 hello http netty demo

Netty应用:快速了解http各版本的特性 HttpServer的小demo

小结

  1. 了解 http 各个版本的解决了什么问题,优缺点,优劣性
  2. 手动编写一个服务端的响应,用postman 查看响应头我们设置的内容
  3. 体验到 netty强大的封装带给我们的便利性
上一篇:使用Netty和动态代理实现一个简单的RPC


下一篇:消息队列高手课 笔记11