一、前言

  1. 实例要求:使用 IDEA 创建 Netty 项目
  2. Netty 服务器在 6668 端口监听,浏览器发出请求 http://localhost:6668/
  3. 服务器可以回复消息给客户端”Hello!我是服务器5”,并对特定请求资源进行过滤。
  4. 目的:Netty 可以做 Http 服务开发,并且理解 Handler 实例和客户端及其请求的关系。
  5. 看老师代码演示

    二、示例代码

    HttpServer

    ```java package com.supkingx.netty.http;

import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel;

/**

  • @description:
  • @Author: wangchao
  • @Date: 2021/12/9 */ public class HttpServer { public static void main(String[] args) {

    1. NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
    2. NioEventLoopGroup workerGroup = new NioEventLoopGroup();
    3. try {
    4. ServerBootstrap serverBootstrap = new ServerBootstrap();
    5. serverBootstrap.group(bossGroup, workerGroup)
    6. .channel(NioServerSocketChannel.class)
    7. .childHandler(new ServerInitializer());
    8. ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
    9. channelFuture.channel().closeFuture().sync();
    10. } catch (Exception e) {
    11. e.printStackTrace();
    12. } finally {
    13. bossGroup.shutdownGracefully();
    14. workerGroup.shutdownGracefully();
    15. }

    } }

  1. <a name="M0sxT"></a>
  2. #### HttpServerCodec
  3. �HttpServerCodec 是 netty 提供的处理 http 的编码解码器<br />通过下图可以看到它继承了 decoder 和 encoder<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1460038/1639058400713-2b9491d2-b064-4d03-b4b8-9d000ac460c0.png#clientId=uc1af5dfa-b417-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=258&id=u9d5887a0&margin=%5Bobject%20Object%5D&name=image.png&originHeight=516&originWidth=2032&originalType=binary&ratio=1&rotation=0&showTitle=false&size=448884&status=done&style=none&taskId=u4b8598b7-b17c-4575-a31a-1f4adaa576c&title=&width=1016)
  4. <a name="fjKYY"></a>
  5. ## ServerInitializer
  6. ```java
  7. package com.supkingx.netty.http;
  8. import io.netty.channel.ChannelInitializer;
  9. import io.netty.channel.ChannelPipeline;
  10. import io.netty.channel.socket.SocketChannel;
  11. import io.netty.handler.codec.http.HttpServerCodec;
  12. /**
  13. * @description:
  14. * @Author: wangchao
  15. * @Date: 2021/12/9
  16. */
  17. public class ServerInitializer extends ChannelInitializer<SocketChannel> {
  18. @Override
  19. protected void initChannel(SocketChannel ch) throws Exception {
  20. // 向管道加入处理器
  21. // 得到管道
  22. ChannelPipeline pipeline = ch.pipeline();
  23. // 加入一个 netty 提供的 httpServerCodec codec =》【coder - decoder】
  24. // HttpServerCodec 说明
  25. // 1、HttpServerCodec 是 netty 提供的处理 http 的编码解码器
  26. pipeline.addLast("MyHttpServerCodec", new HttpServerCodec());
  27. // 2、增加一个自定义的handler
  28. pipeline.addLast("MyTestHttpServerHandler", new HttpServerHandler());
  29. }
  30. }

HttpServerHandler

  1. package com.supkingx.netty.http;
  2. import io.netty.buffer.ByteBuf;
  3. import io.netty.buffer.Unpooled;
  4. import io.netty.channel.ChannelHandlerContext;
  5. import io.netty.channel.SimpleChannelInboundHandler;
  6. import io.netty.handler.codec.http.DefaultFullHttpRequest;
  7. import io.netty.handler.codec.http.DefaultFullHttpResponse;
  8. import io.netty.handler.codec.http.DefaultHttpResponse;
  9. import io.netty.handler.codec.http.FullHttpResponse;
  10. import io.netty.handler.codec.http.HttpHeaderNames;
  11. import io.netty.handler.codec.http.HttpObject;
  12. import io.netty.handler.codec.http.HttpRequest;
  13. import io.netty.handler.codec.http.HttpResponseStatus;
  14. import io.netty.handler.codec.http.HttpVersion;
  15. import io.netty.util.CharsetUtil;
  16. /**
  17. * @description: * SimpleChannelInboundHandler 继承了 ChannelInboundHandlerAdapter
  18. * * HttpObject 客户端和服务端相互通讯的数据被封装成 HttpObject
  19. * @Author: wangchao
  20. * @Date: 2021/12/9
  21. */
  22. public class HttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
  23. /**
  24. * 读取数据
  25. *
  26. * @param ctx 上下文对象,含有 管道pipeline,通道channel,地址
  27. * @param msg 就是客户端发送的数据 默认Object
  28. * @throws Exception
  29. */
  30. @Override
  31. protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
  32. // 判断 msg 是不是 HttpRequest 请求
  33. if (msg instanceof HttpRequest) {
  34. System.out.println("msg 类型=" + msg.getClass());
  35. System.out.println("客户端地址" + ctx.channel().remoteAddress());
  36. // 回复信息给浏览器 【http协议】
  37. ByteBuf content = Unpooled.copiedBuffer("hello,我是服务器", CharsetUtil.UTF_8);
  38. // 构建一个 http 响应,即 httpResponse
  39. FullHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
  40. httpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
  41. httpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
  42. // 将构建好的 response 返回
  43. ctx.writeAndFlush(httpResponse);
  44. }
  45. }
  46. }

演示

启动浏览器并输出 localhost:8899,可以看到浏览器返回值
image.png

疑问

同时发现一个问题,通过服务端日志可以发现浏览器请求了两次,这是为什么?
image.png
通过浏览器开发工具可知,如下,第一次请求服务器,第二次请求网站图标
image.png

选择性接受浏览器请求

通过 获取 uri 过滤特定资源

  1. @Override
  2. protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
  3. // 判断 msg 是不是 HttpRequest 请求
  4. if (msg instanceof HttpRequest) {
  5. System.out.println("msg 类型=" + msg.getClass());
  6. System.out.println("客户端地址" + ctx.channel().remoteAddress());
  7. // 获取
  8. HttpRequest httpRequest = (HttpRequest) msg;
  9. URI uri = new URI(httpRequest.uri());
  10. if ("/favicon.ico".equals(uri.getPath())) {
  11. System.out.println("请求了 favicon.ico,不做响应");
  12. return;
  13. }
  14. // 回复信息给浏览器 【http协议】
  15. ByteBuf content = Unpooled.copiedBuffer("hello,我是服务器", CharsetUtil.UTF_8);
  16. // 构建一个 http 响应,即 httpResponse
  17. FullHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
  18. httpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
  19. httpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
  20. // 将构建好的 response 返回
  21. ctx.writeAndFlush(httpResponse);
  22. }
  23. }

三、杂记

验证浏览器的每一个HTTP链接都会对应一个新 pipeline 和 handler
验证方法:开启多个浏览器窗口实验

  1. @Override
  2. protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
  3. // 判断 msg 是不是 HttpRequest 请求
  4. if (msg instanceof HttpRequest) {
  5. // 验证浏览器的每一个新窗口都会对应一个新 pipeline 和 handler
  6. // 如果是在原窗口不停的请求,都是同一个 pipeline 和 handler
  7. System.out.println("pipeline hashcode" + ctx.pipeline().hashCode() + "; HttpServerHandler hashcode=" + this.hashCode());
  8. 。。。。。。。。。。。。。。。。