Netty-6 Http协议支持-基础使用

由于Tomcat、Jetty等容器比较笨重,而在很多场景下,我们其实并不需要笨重的web容器,所以我们可以自行来开发HTTP协议的服务,由于Netty天生是异步事件驱动的框架,因此开发出的HTTP协议栈也是天生非阻塞的,性能极高。
本节代码参看资料:
https://github.com/cyfonly/netty-http
以及官网示例。

一、Netty对HTTP的支持

要想处理HTTP请求,就需要对HTTP请求信息尽心解析,主要包括:请求头、请求参数、请求的路径等等。
Netty提供了一系列对http的支持,可以查看官网:
https://github.com/netty/netty/tree/4.1/example/src/main/java/io/netty/example/http
https://github.com/netty/netty/tree/4.1/example/src/main/java/io/netty/example/http2

  • HttpRequestDecoder : 请求解码器,把ByteBuf转换成HttpRequest
  • HttpResponseEncoder:相应编码器,把HttpResonse或者是HttpContent转换成ByteBuf
  • HttpServerCodec:是上面两者的结合
  • HttpObjectAggregator:通过它可以把 HttpMessage 和 HttpContent 聚合成一个 FullHttpRequest 或者 FullHttpResponse (取决于是处理请求还是响应),而且它还可以帮助你在解码时忽略是否为“块”传输方式。
    因此,在解析 HTTP POST 请求时,请务必在 ChannelPipeline 中加上 HttpObjectAggregator
  • QueryStringDecoder:把 HTTP uri 分割成 path 和 key-value 参数对,也可以用来解码 Content-Type = “application/x-www-form-urlencoded” 的 HTTP POST。特别注意的是,该 decoder 仅能使用一次

二、HelloWorld

这个例子比较简单,就是在浏览器请求我们使用Netty编写的HTTP服务器,能够返回一个字符串即可。

package com.firewolf.java.io.http.helloworld;

import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;

import com.firewolf.java.io.http.utils.NettyHttpUtils;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
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.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.HttpUtil;

/**
 * 作者:刘兴 时间:2019-05-30
 **/
public final class NettyHttpHelloworldServer {

  private int port;

  public NettyHttpHelloworldServer(int port) {
    this.port = port;
  }

  public void startUp() {
    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)
          .childHandler(
              new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                  socketChannel.pipeline()
                      .addLast(new HttpServerCodec()) //请求解码器和响应编码器,等价于下面两行
//                      .addLast(new HttpRequestDecoder()) //请求解码器
//                      .addLast(new HttpResponseEncoder()) //响应编码器
                      .addLast(new NettyHttpHelloWorldServerHandler());
                }
              }

          );
      Channel ch = b.bind(port).sync().channel();
      System.out.println(String.format("服务已经启动,端口号为:%s", port));
      ch.closeFuture().sync();
    } catch (Exception e) {
      e.printStackTrace();

    } finally {
      //关闭服务
      bossGroup.shutdownGracefully();
      workerGroup.shutdownGracefully();
    }
  }


  class NettyHttpHelloWorldServerHandler extends SimpleChannelInboundHandler<HttpObject> {

    @Override
    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("Hello, Welcome Wto Netty Server !!! ".getBytes()));
        response.headers()
            .set(CONTENT_TYPE, "text/plain;charset=utf-8"); //设置响应类型
        //给客户端响应信息
        NettyHttpUtils.sendResponse(ctx, req, keepAlive, response);
      }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
      cause.printStackTrace();
      ctx.close();
    }
  }

  public static void main(String[] args) throws Exception {
    new NettyHttpHelloworldServer(9998).startUp();
  }
}

其中用到的NettyHttpUtils用来返回信息,内容如下:

package com.firewolf.java.io.http.utils;

import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE;
import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE;

import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpRequest;

/**
 * 作者:刘兴 时间:2019-05-30
 **/
public class NettyHttpUtils {

  public static void sendResponse(ChannelHandlerContext ctx, HttpRequest request, boolean keepAlive,
      FullHttpResponse response) {
    response.headers().set(CONTENT_LENGTH, response.content().readableBytes()); //设置响应长度
    if (keepAlive) {
      if (!request.protocolVersion().isKeepAliveDefault()) {
        response.headers().set(CONNECTION, KEEP_ALIVE);
      }
    } else {
      response.headers().set(CONNECTION, CLOSE);
    }
    ChannelFuture f = ctx.writeAndFlush(response);
    if (!keepAlive) {
      f.addListener(ChannelFutureListener.CLOSE);
    }
  }

}

启动后,浏览器输入:http://localhost:9998/
效果如下:
在这里插入图片描述

三、处理GET和POST请求

我这里仅仅对GET请求和部分类型的POST请求进行了处理,获取了相关的参数,请求类型、路径等,并简单的返回了一些数据。

(一)主启动类

其实主启动和上面的没什么变化,主要是多了一个HttpObjectAggregator,用于解析POST请求参数。

package com.firewolf.java.io.http.request;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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.HttpServerCodec;
import io.netty.handler.codec.http.HttpServerExpectContinueHandler;

/**
 * 作者:刘兴 时间:2019-05-30
 **/
public class NettyHttpServer {

  private int port;

  public NettyHttpServer(int port) {
    this.port = port;
  }

  public void startServer() {
    NioEventLoopGroup boss = new NioEventLoopGroup();
    NioEventLoopGroup worker = new NioEventLoopGroup();
    try {
      ServerBootstrap bootstrap = new ServerBootstrap();
      bootstrap.group(boss, worker);
      bootstrap.channel(NioServerSocketChannel.class)
          .option(ChannelOption.SO_BACKLOG, 1 << 10) //backlog
          .childOption(ChannelOption.SO_KEEPALIVE, true) // 长链接
          .childOption(ChannelOption.TCP_NODELAY, true) //无延迟
          .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
              socketChannel.pipeline()
                  .addLast("req and res codec", new HttpServerCodec()) //等价于下面两个,设置请求解码器和相应编码器
                  .addLast(new HttpObjectAggregator(1 << 20)) //用于处理post请求,否则获取不到请求信息
                  .addLast(new NettyHttpHandler());
            }
          });
      ChannelFuture channelFuture = bootstrap.bind(port).sync();
      System.out.println(String.format("Netty HTTP 服务器已经启动了,监听端口为:%s", port));
      channelFuture.channel().closeFuture().sync();
    } catch (InterruptedException e) {
      e.printStackTrace();
    } finally {
      boss.shutdownGracefully();
      worker.shutdownGracefully();
    }
  }

  public static void main(String[] args) {
    new NettyHttpServer(9999).startServer();
  }
}

(二)处理器

package com.firewolf.java.io.http.request;

import static com.firewolf.java.io.http.request.ContentType.CONTENT_FILE;
import static com.firewolf.java.io.http.request.ContentType.CONTENT_FORM;
import static com.firewolf.java.io.http.request.ContentType.CONTENT_JSON;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpMethod.POST;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.firewolf.java.io.http.utils.NettyHttpUtils;
import com.google.common.base.Charsets;
import io.netty.buffer.Unpooled;
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.HttpMethod;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.multipart.Attribute;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * 作者:刘兴 时间:2019-05-30
 **/
public class NettyHttpHandler extends SimpleChannelInboundHandler<HttpObject> {

  //浏览器发起请求的时候,会先请求一次图标,这里先不做处理
  private static final String FAVICON_ICO = "/favicon.ico";

  @Override
  public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
    System.out.println("接收到了Http请求了.....");
    //解析请求参数
    if (msg instanceof HttpRequest) {
      HttpRequest request = (HttpRequest) msg;
      boolean keepAlive = HttpUtil.isKeepAlive(request);
      String uri = request.uri();
      if (uri.equals(FAVICON_ICO)) { //不处理图标
        return;
      }
      QueryStringDecoder paramDecoder = new QueryStringDecoder(uri);
      System.out.println("请求路径为:" + paramDecoder.path());
      //判断请求类型
      HttpMethod method = request.method();
      System.out.println("请求类型:" + method.toString());
      System.out.println("接受到请求中的参数如下:");
      if (method == GET) { //get请求
        //使用Netty提供的解析URL的工具来解析URL中的参数
        Map<String, List<String>> parameters = paramDecoder.parameters();  //解析出所有的参数
        parameters.entrySet().forEach(x -> {
          System.out
              .println(String.format("%s : %s", x.getKey(), Arrays.toString(x.getValue().toArray(new String[0]))));
        });

      } else if (method == POST) { //处理post请求
        FullHttpRequest fullRequest = (FullHttpRequest) msg;
        String contentType = fullRequest.headers().get(CONTENT_TYPE).split(";")[0]; //参数类型
        switch (contentType) {
          case CONTENT_JSON: { //JSON数据
            //解析JSON字符串
            String jsonStr = fullRequest.content().toString(Charsets.UTF_8);
            JSONObject obj = JSON.parseObject(jsonStr);
            obj.keySet().forEach(x -> {
              Object o = obj.get(x);
              if (o instanceof JSONArray) {
                System.out.print("数组: ");
              }
              System.out.println(x + "," + o);
            });
            break;
          }
          case CONTENT_FORM: { //表单提交数据
            //解析表单参数
            HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(fullRequest);
            List<InterfaceHttpData> datas = decoder.getBodyHttpDatas(); //获取请求数据
            datas.forEach(x -> {
              try {
                if (x.getHttpDataType() == HttpDataType.Attribute) {
                  Attribute attribute = (Attribute) x;
                  System.out.println(attribute.getName() + "=" + attribute.getValue());
                }
              } catch (IOException e) {
                e.printStackTrace();
              }
            });

            break;
          }
          case CONTENT_FILE: { //文件上传
            break;
          }
          default:
            return;
        }
      }

      //回写信息
      //构造响应数据
      FullHttpResponse response = new DefaultFullHttpResponse(request.protocolVersion(), OK,
          Unpooled.wrappedBuffer("我受到了你的请求!!! ".getBytes()));
      response.headers()
          .set(CONTENT_TYPE, "text/plain;charset=utf-8"); //设置响应类型
      NettyHttpUtils.sendResponse(ctx, request, keepAlive, response);
    }
  }


  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    cause.printStackTrace();
    ctx.close();
  }
}

其中文件上传的暂时没做处理。

(三)辅助工具类

package com.firewolf.java.io.http.request;

/**
 * 作者:刘兴 时间:2019-05-30
 **/
public class ContentType {

  //提交的json数据
  public final static String CONTENT_JSON = "application/json";

  //表单提交
  public final static String CONTENT_FORM = "application/x-www-form-urlencoded";

  //文件上传
  public final static String CONTENT_FILE = "multipart/form-data";

}

这个类就是存放了一些类型。
启动,使用postman发起请求:
在这里插入图片描述
结果:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/mail_liuxing/article/details/90670006