Netty搭建Http服务器

511 阅读3分钟

Netty搭建Http服务器

  • HTTP服务器在我们日常开发中,常见的实现方式就是实现一个Java Web项目,基于Nginx+Tomcat的方式就可以提供HTTP服务。但是很多场景是非Web容器的场景,这个时候再使用Tomcat就大材小用了。这个时候就可以使用基于Netty的HTTP协议。而且基于Netty开发的HTTP服务器有如下优势:
    • Netty的线程模型和异步非阻塞特性能够支持高并发
    • 相比于Tomcat HTTP,Netty HTTP更加轻量、小巧、可靠,占用资源更少

image.png

HTTP 协议抽象

image.png

HTTP 协议抽象-请求

image.png

HTTP 协议抽象-响应

image.png

HTTP 协议抽象-Content-Length,chunked

image.png

HTTP 协议抽象-响应压缩

image.png

Netty HTTP 协议抽象的实现-请求和响应

image.png

HttpRequestDecoder & HttpObjectAggregator & HttpResponseEncoder

image.png

代码实现

服务端

HttpServerHandler

package com.bxg.netty.handler.server;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

import com.alibaba.fastjson.JSONObject;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.multipart.Attribute;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.DiskFileUpload;
import io.netty.handler.codec.http.multipart.FileUpload;
import io.netty.handler.codec.http.multipart.HttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import lombok.extern.slf4j.Slf4j;

/**
 *@Description
 *@Author 郑懿
 *@Date 2023/4/2 17:45
 **/
@Slf4j
public class MyHttpServerInBoundHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

   private static final HttpDataFactory HTTP_DATA_FACTORY = new DefaultHttpDataFactory(DefaultHttpDataFactory.MAXSIZE);

   static {
      DiskFileUpload.baseDirectory = "D:\netty\upload";
   }

   @Override
   protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest httpRequest) throws Exception {
      //1.根据请求类型做出处理
      HttpMethod type = httpRequest.method();
      if (type.equals(HttpMethod.GET)) {
         //Get请求
         parseGet(httpRequest);
      }
      else if (type.equals(HttpMethod.POST)) {
         //post请求
         parsePost(httpRequest);
      }
      else {
         log.error("不支持的请求方式,{}", type);
      }

      StringBuilder sb = new StringBuilder();
      sb.append("<html>");
      sb.append("<head>");
      sb.append("</head>");
      sb.append("<body>");
      sb.append("<h3>Success</h3>");
      sb.append("</body>");
      sb.append("</html>");

      //给客户端写数据
      writeResponse(ctx, httpRequest, HttpResponseStatus.OK, sb.toString());
   }

   /**
    * 给客户端响应
    * @param ctx
    * @param fullHttpRequest
    * @param status
    * @param msg
    */
   private void writeResponse(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, HttpResponseStatus status, String msg) {
      //创建一个默认的响应对象
      FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status);
      //写入数据
      response.content().writeBytes(msg.getBytes(StandardCharsets.UTF_8));
      //设置响应头--content-type
      response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=utf-8");
      //设置内容长度--content-length
      HttpUtil.setContentLength(response, response.content().readableBytes());
      boolean keepAlive = HttpUtil.isKeepAlive(fullHttpRequest);
      if (keepAlive) {
         response.headers().set(HttpHeaderNames.CONNECTION, "keep-alive");
      }
      ctx.writeAndFlush(response);
   }

   /**
    * 处理get请求
    * @param httpRequest
    */
   private void parseGet(FullHttpRequest httpRequest) {
      //通过请求url获取参数信息
      parseKvStr(httpRequest.uri(), true);
   }

   /**
    * 从url中获取参数信息
    * @param uri 请求的url
    * @param hasPath
    */
   private void parseKvStr(String uri, boolean hasPath) {
      QueryStringDecoder queryStringDecoder = new QueryStringDecoder(uri, StandardCharsets.UTF_8, hasPath);
      Map<String, List<String>> parameters = queryStringDecoder.parameters();
      //封装参数,执行业务,此处打印即可
      parameters.entrySet().stream().forEach(item -> {
         log.info("item:{}={}", item.getKey(), item.getValue());
      });
   }

   /**
    * 处理post请求
    * application/json
    * application/x-www-form-urlencoded
    * multipart/form-data
    * @param httpRequest
    */
   private void parsePost(FullHttpRequest httpRequest) {
      String contentType = getContentType(httpRequest);
      switch (contentType) {
      case "application/json":
         parseJson(httpRequest);
         break;
      case "application/x-www-form-urlencoded":
         parseFormData(httpRequest);
         break;
      case "multipart/form-data":
         parseMultipart(httpRequest);
         break;
      default:
         log.error("不支持的数据类型:{}", contentType);
         break;
      }

   }


   /**
    * 处理文件上传
    * 在该方法中的解析方式,同样也适用于解析普通的表单提交请求
    * 通用(普通post,文件上传)
    * @param httpRequest
    */
   private void parseMultipart(FullHttpRequest httpRequest) {
      HttpPostRequestDecoder httpPostRequestDecoder = new HttpPostRequestDecoder(HTTP_DATA_FACTORY, httpRequest);
      //判断是否是multipart
      if (httpPostRequestDecoder.isMultipart()) {
         //获取body中的数据
         List<InterfaceHttpData> bodyHttpDatas = httpPostRequestDecoder.getBodyHttpDatas();
         for (InterfaceHttpData dataItem : bodyHttpDatas) {
            //判断表单项的类型
            InterfaceHttpData.HttpDataType httpDataType = dataItem.getHttpDataType();
            if (httpDataType.equals(InterfaceHttpData.HttpDataType.Attribute)){
               //普通表单项,直接获取数据
               Attribute attribute = (Attribute) dataItem;
               try {
                  log.info("表单项名称:{},表单项值:{}",attribute.getName(),attribute.getValue());
               } catch (IOException e) {
                  log.error("获取表单项数据错误,msg={}",e.getMessage());
               }
            } else if (httpDataType.equals(InterfaceHttpData.HttpDataType.FileUpload)){
               //文件上传项,将文件保存到磁盘
               FileUpload fileUpload = (FileUpload) dataItem;
               //获取原始文件名称
               String filename = fileUpload.getFilename();
               //获取表单项名称
               String name = fileUpload.getName();
               log.info("文件名称:{},表单项名称:{}",filename,name);
               //将文件保存到磁盘
               if (fileUpload.isCompleted()) {
                  try {
                     String path = DiskFileUpload.baseDirectory + File.separator + filename;
                     fileUpload.renameTo(new File(path));
                  } catch (IOException e) {
                     log.error("文件转存失败,msg={}",e.getMessage());
                  }
               }
            }
         }
      }
   }

   /**
    * 处理表单数据
    * @param httpRequest
    */
   private void parseFormData(FullHttpRequest httpRequest) {
      //两个部分有数据  uri,body
      parseKvStr(httpRequest.uri(), true);
      parseKvStr(httpRequest.content().toString(StandardCharsets.UTF_8), false);
   }

   /**
    * 处理json数据
    * @param httpRequest
    */
   private void parseJson(FullHttpRequest httpRequest) {
      String jsonStr = httpRequest.content().toString(StandardCharsets.UTF_8);
      JSONObject jsonObject = JSONObject.parseObject(jsonStr);
      jsonObject.entrySet().stream().forEach(item -> {
         log.info("item:{}={}", item.getKey(), item.getValue());
      });
   }

   /**
    * 获取请求数据类型
    * @param httpRequest
    * @return
    */
   private String getContentType(FullHttpRequest httpRequest) {
      HttpHeaders headers = httpRequest.headers();
      String contentType = headers.get(HttpHeaderNames.CONTENT_TYPE);
      return contentType.split(";")[0];
   }
}

HttpServer

package com.bxg.netty;

import com.bxg.netty.handler.server.MyHttpServerInBoundHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

/**
 *@Description netty服务端
 *@Author 郑懿
 *@Date 2023/4/2 14:46
 **/
public class HttpServer {

   public static void main(String[] args) {

      startServer(8888);
   }

   private static void startServer(int port) {
      EventLoopGroup boss = new NioEventLoopGroup(1);
      EventLoopGroup worker = new NioEventLoopGroup(); //默认线程是cpu核心线程数的两倍

      try {
         ServerBootstrap bootstrap = new ServerBootstrap();
         bootstrap.group(boss,worker)
               .channel(NioServerSocketChannel.class)
               .handler(new LoggingHandler(LogLevel.INFO))
               .childHandler(new ChannelInitializer<SocketChannel>() {
                  @Override
                  protected void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline pipeline = ch.pipeline();
                     pipeline.addLast(new HttpResponseEncoder());
                     pipeline.addLast(new HttpRequestDecoder());
                     //文件上传需要设置大一点,单位是字节
                     pipeline.addLast(new HttpObjectAggregator(1024 * 1024 * 8));//8M
                     pipeline.addLast(new MyHttpServerInBoundHandler());
                  }
               });
         ChannelFuture future = bootstrap.bind(port).sync();
         //监听端口关闭
         future.channel().closeFuture().sync();
      }
      catch (InterruptedException e) {
         throw new RuntimeException(e);
      }
      finally {
         boss.shutdownGracefully();
         worker.shutdownGracefully();
      }
   }

}

http页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试 netty http server</title>
</head>
<body>
    <form action="http://localhost:8888/test" method="post" enctype="multipart/form-data">
        <label for="username">用户名:</label><input id="username" type="text" name="username"><br/>
        <label for="password">密码:</label><input id="password" type="password" name="password"><br/>
        <label for="email">邮箱:</label><input id="email" type="email" name="email"><br/>
        <label for="address">地址:</label><input id="address" type="text" name="address"><br/>
        <label for="pic">选择文件:</label><input id="pic" type="file" name="pic"><br/>

        <input type="submit" value="提交">
    </form>
</body>
</html>