Netty-搭建一个简单的HTTP服务器

前言

前面几篇文章用Netty搭建了不同传输协议的的消息接收发送服务并解决的粘包半包问题。

Netty-搭建一个简单的消息接收发送服务icon-default.png?t=N6B9https://blog.csdn.net/yimint/article/details/132061057?spm=1001.2014.3001.5501Netty-搭建一个简单的Json协议消息接收发送服务icon-default.png?t=N6B9https://blog.csdn.net/yimint/article/details/132075254?spm=1001.2014.3001.5501Netty-搭建一个简单的Protobuf协议消息接收发送服务icon-default.png?t=N6B9https://blog.csdn.net/yimint/article/details/132145975?spm=1001.2014.3001.5501

这篇文章用Netty搭建一个简单的HTTP服务器。


一、Netty——HTTP

在这之前先了解Http协议和特点以及Netty对HTTP的封装,相关文章网上已经有非常详细的资料了,我推荐以下两篇文章:

Netty之Httpicon-default.png?t=N6B9https://zhuanlan.zhihu.com/p/135239211netty对http协议解析原理解析icon-default.png?t=N6B9https://cloud.tencent.com/developer/article/1035923

二、Netty搭建HTTP服务器

1.引入依赖

    <dependencies>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.22</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.21.Final</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.20.1</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>4.4.3</version>
        </dependency>
    </dependencies>

2.服务端

public class EchoHttpServer {
    private final static Logger LOGGER = LoggerFactory.getLogger(EchoHttpServer.class);
    public static final String SERVER_IP = "127.0.0.1";
    public static final int SERVER_PORT = 54123;

    public static void main(String[] args) {

        ServerBootstrap b = new ServerBootstrap();
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workGroup = new NioEventLoopGroup();
        try {
            b.group(bossGroup, workGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            //请求解码器和响应编码器,等价于下面两行
                            // pipeline.addLast(new HttpServerCodec());
                            //请求解码器
                            pipeline.addLast(new HttpRequestDecoder());
                            //响应编码器
                            pipeline.addLast(new HttpResponseEncoder());
                            // HttpObjectAggregator 将HTTP消息的多个部分合成一条完整的HTTP消息
                            // HttpObjectAggregator 用于解析Http完整请求
                            // 把多个消息转换为一个单一的完全FullHttpRequest或是FullHttpResponse,
                            // 原因是HTTP解码器会在解析每个HTTP消息中生成多个消息对象
                            // 如 HttpRequest/HttpResponse,HttpContent,LastHttpContent
                            pipeline.addLast(new HttpObjectAggregator(65535));
                            pipeline.addLast(new ChunkedWriteHandler());
                            // 自定义的业务handler
                            pipeline.addLast(new HttpEchoHandler());
                        }
                    });
            Channel ch = b.bind(SERVER_PORT).sync().channel();
            LOGGER.info("HTTP ECHO 服务已经启动 http://{}:{}/", "127.0.0.1", SERVER_PORT);
            // 等待服务端监听到端口关闭
            ch.closeFuture().sync();
        } catch (Exception ex) {
            LOGGER.error(ExceptionUtil.stacktraceToString(ex));
        }finally {
            workGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}
public class HttpEchoHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
    private final static Logger LOGGER = LoggerFactory.getLogger(HttpEchoHandler.class);
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
        if (!request.decoderResult().isSuccess()) {//判断解码是否成功
            HttpProtocolHelper.sendError(ctx, BAD_REQUEST);
            return;
        }
        /**
         * 缓存HTTP协议的版本号
         */
        HttpProtocolHelper.cacheHttpProtocol(ctx, request);

        Map<String, Object> echo = new HashMap<>();
        // 1.获取URI
        String uri = request.uri();
        echo.put("request uri", uri);
        LOGGER.info("request uri:{}", uri);
        // 2.获取请求方法
        HttpMethod method = request.method();
        echo.put("request method", method.toString());
        LOGGER.info("request method:{}", method);
        // 3.获取请求头
        Map<String, Object> echoHeaders = new HashMap<>();
        HttpHeaders headers = request.headers();
        for (Map.Entry<String, String> header : headers.entries()) {
            echoHeaders.put(header.getKey(), header.getValue());
        }

        echo.put("request header", echoHeaders);
        LOGGER.info("request header:{}", echoHeaders);
        /**
         * 获取uri请求参数
         */
        Map<String, Object> uriDatas = paramsFromUri(request);
        echo.put("paramsFromUri", uriDatas);
        LOGGER.info("request paramsFromUri:{}", uriDatas);
        // 处理POST请求
        if (POST.equals(request.method())) {
            /**
             * 获取请求体数据到 map
             */
            Map<String, Object> postData = dataFromPost(request);
            echo.put("dataFromPost", postData);
            LOGGER.info("request dataFromPost:{}", postData);
        }


        /**
         * 回显内容转换成json字符串
         */
        String sendContent = JSONUtil.toJsonStr(echo);
        /**
         * 发送回显内容到客户端
         */
        HttpProtocolHelper.sendJsonContent(ctx, sendContent);
    }

    /*
     * 从URI后面获取请求的参数
     */
    private Map<String, Object> paramsFromUri(FullHttpRequest fullHttpRequest) {
        Map<String, Object> params = new HashMap<String, Object>();
        // 把URI后面的参数串,分割成key-value形式
        QueryStringDecoder decoder = new QueryStringDecoder(fullHttpRequest.uri());
        // 提取key-value形式的参数串
        Map<String, List<String>> paramList = decoder.parameters();
        //迭代key-value形式的参数串
        for (Map.Entry<String, List<String>> entry : paramList.entrySet()) {
            params.put(entry.getKey(), entry.getValue().get(0));
        }
        return params;
    }

    /*
     * 获取POST方式传递的请求体数据
     */
    private Map<String, Object> dataFromPost(FullHttpRequest fullHttpRequest) {

        Map<String, Object> postData = null;
        try {
            String contentType = fullHttpRequest.headers().get("Content-Type").trim();
            //普通form表单数据,非multipart形式表单
            if (contentType.contains("application/x-www-form-urlencoded")) {
                postData = formBodyDecode(fullHttpRequest);
            }
            //multipart形式表单
            else if (contentType.contains("multipart/form-data")) {
                postData = formBodyDecode(fullHttpRequest);
            }
            // 解析json数据
            else if (contentType.contains("application/json")) {
                postData = jsonBodyDecode(fullHttpRequest);
            } else if (contentType.contains("text/plain")) {
                ByteBuf content = fullHttpRequest.content();
                byte[] reqContent = new byte[content.readableBytes()];
                content.readBytes(reqContent);
                String text = new String(reqContent, StandardCharsets.UTF_8);
                postData = new HashMap<>();
                postData.put("text", text);
            }
            return postData;
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }

    /*
     * 解析from表单数据
     */
    private Map<String, Object> formBodyDecode(FullHttpRequest fullHttpRequest) {
        Map<String, Object> params = new HashMap<String, Object>();
        try {
            HttpPostRequestDecoder decoder =
                    new HttpPostRequestDecoder(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE),
                            fullHttpRequest,
                            CharsetUtil.UTF_8);

            List<InterfaceHttpData> postData = decoder.getBodyHttpDatas();
            if (postData == null || postData.isEmpty()) {
                decoder = new HttpPostRequestDecoder(fullHttpRequest);
                if (fullHttpRequest.content().isReadable()) {
                    String json = fullHttpRequest.content().toString(CharsetUtil.UTF_8);
                    params.put("body", json);
                }
            }

            for (InterfaceHttpData data : postData) {
                if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
                    MixedAttribute attribute = (MixedAttribute) data;
                    params.put(attribute.getName(), attribute.getValue());
                } else if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.FileUpload) {
                    MixedFileUpload fileUpload = (MixedFileUpload) data;
                    String filename = fileUpload.getFilename();
                    byte[] content = fileUpload.get();
                    String contentType = fileUpload.getContentType();
                    params.put("filename", filename);
                    params.put("contentType", contentType);
                    params.put("content", Arrays.toString(content));
                    fileUpload.release();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return params;
    }

    /*
     * 解析json数据(Content-Type = application/json)
     */
    private Map<String, Object> jsonBodyDecode(FullHttpRequest fullHttpRequest) throws UnsupportedEncodingException {
        ByteBuf content = fullHttpRequest.content();
        byte[] reqContent = new byte[content.readableBytes()];
        if (reqContent.length == 0) {
            return null;
        }
        Map<String, Object> params = new HashMap<String, Object>();
        content.readBytes(reqContent);
        String strContent = new String(reqContent, StandardCharsets.UTF_8);

        JSONObject jsonParams = JSONUtil.parseObj(strContent);

        for (Object key : jsonParams.keySet()) {
            params.put(key.toString(), jsonParams.get(key));
        }

        return params;
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        if (ctx.channel().isActive()) {
            HttpProtocolHelper.sendError(ctx, INTERNAL_SERVER_ERROR);
        }
    }
}

 三、请求测试

 启动HTTP服务器

 浏览器GET请求服务


 postman POST请求服务

总结

至此,一个简单的基于Netty的HTTP服务器就搭完了,HTTP相关知识点还是很重要的,为后续基于Netty搭建一个轻量级的WEB框架打下基础。

项目地址:https://github.com/YIminta/Netty-learning

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值