Netty-6 Http protocol support - Basics

Because Tomcat, Jetty and other containers cumbersome, and in many scenes, we actually do not need bulky web container, so we can be self-development services HTTP protocol, since Netty inherently asynchronous event-driven framework, developed HTTP protocol stack is inherently non-blocking, high performance.
Information in this section with reference to the code:
https://github.com/cyfonly/netty-http
and exemplary official network.

A, Netty HTTP support

To handle HTTP requests, it is necessary to parse HTTP request dedicated information, including: a request header, the request parameter, the route request and the like.
Netty provides a range of support for http, you can check the official website:
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: request decoder to convert into HttpRequest ByteBuf
  • HttpResponseEncoder: a respective encoder, converts into ByteBuf HttpResonse or HttpContent
  • HttpServerCodec: a combination of both the above
  • HttpObjectAggregator: HttpMessage and through which can be aggregated into a FullHttpRequest HttpContent or FullHttpResponse (depending on a processing request or a response), and it helps you ignore whether a "block" in the transmission time of decoding.
    Therefore, when parsing HTTP POST request, be sure to add in HttpObjectAggregator in ChannelPipeline
  • QueryStringDecoder: HTTP uri into the path parameters and key-value pairs may be used to decode Content-Type = "application / x-www-form-urlencoded" the HTTP POST. Of particular note is that the decoder only be used once

Two, HelloWorld

This example is relatively simple, we request that the browser uses the HTTP server written in Netty, you can return a string can be.

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();
  }
}

Wherein NettyHttpUtils used to return information, as follows:

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);
    }
  }

}

After starting the browser input: HTTP: // localhost: 9998 /
results are as follows:
Here Insert Picture Description

Third, the process GET and POST requests

I am here just to GET requests and POST requests some types were processed to obtain the relevant parameters, the request type, path, etc., and simply return some data.

(A) the master boot class

In fact, no change in the master boot and above, mainly one more HttpObjectAggregator, for parsing POST request parameters.

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();
  }
}

(B) processor

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();
  }
}

Which temporarily uploaded files do not deal with.

(C) aid class

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";

}

This class is stored for some types.
Start using the postman initiate a request:
Here Insert Picture Description
Result:
Here Insert Picture Description

Guess you like

Origin blog.csdn.net/mail_liuxing/article/details/90670006