写点什么

netty 系列之: 轻轻松松搭个支持中文的服务器

发布于: 2 小时前

简介之前讲了那么多关于 netty 的文章,都是讲 netty 的底层原理和实现,各位小伙伴一定都在想了,看了这么多篇文章,netty 到底能干啥呢?今天让我们来使用 netty 简简单单搭一个支持中文的服务器,展示一下 netty 的威力。


netty 的 HTTP 支持今天我们搭的服务器是支持 HTTP1.1 的服务器。在 netty 中搭建服务器就像是拼房子,找到合适的工具就可以事半功倍。那么要搭建 HTTP 的房子,netty 提供了什么样的工具呢?


在讲解 netty 对 HTTP 的支持之前,我们先看一下 HTTP 的版本发展情况。


HTTP 的全称是 Hypertext Transfer Protocol,是在 1989 年 World Wide Web 发展起来之后出现的标准协议,用来在 WWW 上传输数据。HTTP/1.1 是 1997 年在原始的 HTTP 协议基础上进行的补充和优化。


到了 2015 年,为了适应快速发送的 web 应用和现代浏览器的需求,发展出了新的 HTTP/2 协议,主要在手机浏览器、延时处理、图像处理和视频处理方面进行了优化。


基本上所有的现代浏览器都支持 HTTP/2 协议了,但是还有很多应用程序使用的是老的 HTTP/1.1 协议。netty 为 HTTP2 和 HTTP1 提供了不同的支持包,对于 HTTP1 的支持包叫做 netty-codec-http,对 HTTP2 支持的包叫做 netty-codec-http2。


本文会讲解 netty 对 HTTP1 的支持,将会在后续的文章中继续 HTTP2 的介绍。


netty-codec-http 提供了对 HTTP 的非常有用的一些封装。


首先是代表 HTTP 中传输对象的类 HttpObject,这个类代表着传输中的所有对象。继承这个类的对象有两个非常重要的对象,分别是 HttpMessage 和 HttpContent。


HttpMessage 可能跟我想象的不太一样,它实际上只包含了两部分内容,分别是 HttpVersion 和 HttpHeaders,但是并不包含任何内容。


public interface HttpMessage extends HttpObject {


HttpVersion protocolVersion();
HttpMessage setProtocolVersion(HttpVersion version);
HttpHeaders headers();
复制代码


}


这里 HttpVersion 只支持 HTTP/1.0 和 HTTP/1.1 协议。而 HttpHeaders 就是对 HTTP 请求中头对象的封装。


HttpMessage 的子类是 HttpRequest 和 HttpResponse,所以这两个类本身是不带请求内容的。


而具体请求的内容是在 HttpContent 中,HttpContent 继承自 ByteBufHolder,表示它中间可以带有 ByteBuf 的内容信息。


而 HttpContent 真正的实现类就是 DefaultFullHttpRequest 和 DefaultFullHttpResponse,这两个内包含了 HTTP 头和 HTTP 请求响应内容信息。


那么问题来了,为什么要把 HTTP 头和 HTTP 内容分开呢?


这就涉及到 HTTP1.1 中消息传输中的压缩机制了。为了提升传输的效率,一般来说在传输的的过程中都会对象消息进行压缩,但是对于 HTTP1.1 来说,头部的内容是没办法压缩的,只能压缩 content 部分,所以需要区别对待。


netty 中使用 HTTP 的原理我们知道 netty 底层是客户端和服务器端构建通道,通过通道来传输 ByteBuf 消息。那么 netty 是怎么支持 HTTP 请求呢?


当客户端向服务器端发送 HTTP 请求之后,服务器端需要把接收到的数据使用解码器解码成为可以被应用程序使用的各种 HttpObject 对象,从而能够在应用程序中对其解析。


netty 提供了 HttpResponseEncoder 和 HttpRequestDecoder 类,来对 HTTP 的消息进行编码和解码。


如果不想分别使用两个类来进行编码和解码,netty 还提供了 HttpServerCodec 类来进行编码和解码工作。


这个类包含了 HttpRequestDecoder 和 HttpResponseEncoder 两部分的工作,可以同时用来进行编码和解码。


100 (Continue) Status 在 HTTP 中有一个独特的功能叫做,100 (Continue) Status,就是说 client 在不确定 server 端是否会接收请求的时候,可以先发送一个请求头,并在这个头上加一个”100-continue”字段,但是暂时还不发送请求 body。直到接收到服务器端的响应之后再发送请求 body。


为了处理这种请求,netty 提供了一个 HttpServerExpectContinueHandler 对象,用来处理 100 Status 的情况。


当然,如果你的客户端没有这种请求,那么可以直接使用 HttpObjectAggregator 来将 HttpMessage 和 HttpContent 和合并成为 FullHttpRequest 或者 FullHttpResponse。


为 netty 搭建 HTTP 服务器有了上面的工作,我们就可以使用 netty 搭建 http 服务器了。最关键的一点就是在 HttpRequestServerInitializer 添加对应的 codec 和自定义 handler。


public void initChannel(SocketChannel ch) {    ChannelPipeline p = ch.pipeline();    p.addLast(new HttpServerCodec());    p.addLast(new HttpServerExpectContinueHandler());    p.addLast(new HttpRequestServerHandler());}
复制代码


在自定义的 handler 中,我们需要实现一个功能,就是当收到客户端的请求时候,需要返回给客户端一段欢迎语。


首先将获得的 HttpObject 转换成为 HttpRequest 对象,然后根据请求对象构建一个 DefaultFullHttpResponse 对象,然后设置该 response 对象的头,最后将该对象写到 channel 中。


对应的关键代码如下:


private static final byte[] CONTENT = "欢迎来到 www.flydean.com!".getBytes(StandardCharsets.UTF_8);


public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) {    if (msg instanceof HttpRequest) {        HttpRequest req = (HttpRequest) msg;
boolean keepAlive = HttpUtil.isKeepAlive(req); FullHttpResponse response = new DefaultFullHttpResponse(req.protocolVersion(), OK, Unpooled.wrappedBuffer(CONTENT)); response.headers()
复制代码


// .set(CONTENT_TYPE, TEXT_PLAIN).set(CONTENT_TYPE, "text/plain;charset=utf-8").setInt(CONTENT_LENGTH, response.content().readableBytes());


        if (keepAlive) {            if (!req.protocolVersion().isKeepAliveDefault()) {                //设置header connection=keep-alive                response.headers().set(CONNECTION, KEEP_ALIVE);            }        } else {            // 如果keepAlive是false,则设置header connection=close            response.headers().set(CONNECTION, CLOSE);        }        ChannelFuture f = ctx.write(response);        if (!keepAlive) {            f.addListener(ChannelFutureListener.CLOSE);        }    }}
复制代码


上面的关键代码中 CONTENT 包含了中文字符串,我们使用 getBytes 将其转换成了 UTF-8 编码的 byte 数组。那么如果要想客户端能够正确识别 UTF-8 编码,需要在 response 的 header 中设置内容类型文件为:”text/plain;charset=utf-8″。


最后,使用下面的代码启动 server:


// server 配置 EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.option(ChannelOption.SO_BACKLOG, 1024);b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).handler(new LoggingHandler(LogLevel.INFO)).childHandler(new HttpRequestServerInitializer());


        Channel ch = b.bind(PORT).sync().channel();        log.info("请打开你的浏览器,访问 http://127.0.0.1:8000/");        ch.closeFuture().sync();    } finally {        bossGroup.shutdownGracefully();        workerGroup.shutdownGracefully();    }
复制代码


总结现在,使用你的浏览器访问你搭建的服务器地址,你就可以得到”欢迎来到 www.flydean.com!”。 到此一个简单的 netty 服务器就完成了。


本文的例子可以参考:learn-netty4


本文已收录于 http://www.flydean.com/19-netty-http-client-request/


最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!


欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

发布于: 2 小时前阅读数: 5
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
netty系列之:轻轻松松搭个支持中文的服务器