Netty-如何写一个Http服务器

前言

动机

最近在学习Netty框架,发现Netty是支持Http协议的。加上以前看过Spring-MVC的源码,就想着二者能不能结合一下,整一个简易的web框架(PS:其实不是整,是抄)

效果

项目地址:terabithia

0.3版本使用效果如下,其实就是Spring-MVC的Controller的写法

@RestController
@RequestMapping(value = "/hello")
public class HelloController {

    /**
     * request url:/hello/testGet?strParam=test&intParam=1&floatParam=2&doubleParam=3
     */
    @RequestMapping(value = "/testGet", method = {RequestMethod.GET})
    public Object testGet(ParamWrapperRequest request, String strParam, Integer intParam, Float floatParam, Double doubleParam) throws Exception{
        System.out.println(request.getParameterNames());
        // 获取参数
        System.out.println("strParam:" + strParam);
        System.out.println("intParam:" + intParam);
        System.out.println("floatParam:" + floatParam);
        System.out.println("doubleParam:" + doubleParam);

        System.out.println("testGet");
        return request.getParameterMap();
    }
}

前置条件

以下列出各个版本需要掌握的知识:

  • 0.1版本:Netty
  • 0.2版本:Netty
  • 0.3版本:Netty + Spring-MVC

0.1版本

0.1版本就是Netty原生API对Http协议的支持

如何实现

HttpServer初始化Netty服务, HttpServerCodecHttpObjectAggregator提供了对Http的支持, HttpServerHandler处理业务逻辑。

public class HttpServer {

    private static final int PORT = 8080;

    public static void main(String[] args) {
        final EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        final EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            final ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    // 临时存放已完成三次握手的请求的队列
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline()
                                    .addLast(new HttpServerCodec())
                                    //把多个消息转换为一个单一的FullHttpRequest或是FullHttpResponse
                                    .addLast(new HttpObjectAggregator(65536))
                                    .addLast(new HttpServerHandler());
                        }
                    });

            final Channel ch = b.bind(PORT).sync().channel();
            ch.closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

}

public class HttpServerHandler extends SimpleChannelInboundHandler {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
        // 业务逻辑处理。。。
        String result = "hello world";

        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.OK);
        response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
        response.content().writeBytes(result.getBytes(StandardCharsets.UTF_8));
        response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes());
        response.headers().set(CONNECTION, CLOSE);

        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }
}

局限性

无法专注于业务处理,HttpServerHandler需要处理Netty的API和业务逻辑

0.2版本

0.1版本在同一个Handler中处理业务和网络是不合适的,想办法将HttpServerHandler解耦, 划分Handler和Controller。Handler处理Netty API,Controller处理业务逻辑。

如何实现

此版本不讲代码,讲思路(PS:主要是我懒得写)

Netty-如何写一个Http服务器
  • HttpServerHandler处理Netty API,转发请求到Controller以及处理Controller的返回值
  • 获取请求uri,根据uri找到相对应的Controller以及方法
  • 调用Controller的方法
  • 处理Controller的返回值,封装成HttpResponse返回
  • Controller处理业务逻辑,怎么在Spring-MVC用Controller,在这里就怎么用 Controller的主要问题是如何根据uri找到相对应的Controller以及方法,下面给出两种思路:
  • 自定义接口方式,并有Map存放uri与Controller的关系
// 表示是Controller的接口
public interface Action {
    Object doAction(FullHttpRequest request);
}
// 业务Controller
public class HelloAction implements Action{
    @Override
    public Object doAction(FullHttpRequest request) {
        // 业务逻辑处理
        return null;
    }
}
// 使用Map存放uri与Controller的关系
Map actionMap = new ConcurrentHashMap<>();
actionMap.put("/hello", new HelloAction());

// 而在HttpServerHandler调用就更简单了,不用反射
actionMap.get(uri).doAction(request);
  1. 自定义注解方式
// 表示是Controller的注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {

    String value() default "";

}

// 业务Controller
@RequestMapping("/hello")
public class HelloController {
    @RequestMapping("/index")
    public FullHttpResponse index(FullHttpRequest request) {
        return null;
    }
}

/*
 * 而在HttpServerHandler调用
 */
Class clazz = obj.getClass();
// 获取类上的注解
RequestMapping mapping = clazz.getAnnotation(RequestMapping.class);
if (mapping != null) {
    String mappingUri = mapping.value();
    // 获取controller的所有方法
    for (Method actionMethod : clazz.getMethods()) {
        // 获取方法上的注解
        RequestMapping subMapping = actionMethod.getAnnotation(RequestMapping.class);
        if (subMapping != null) {
            String subMappingUri = subMapping.value();
            // 如果uri符合注解规则,则反射调用方法
            if (uri.equalsIgnoreCase(mappingUri + subMappingUri)) {
                return (FullHttpResponse) actionMethod.invoke(obj, request);
            }
        }
    }
}

局限性

没有一般web框架的过滤器,参数处理,返回值处理等。业务代码仍然深度依赖Netty API

0.3版本

结构

依赖:Netty + SpringBoot

处理流程图如下:

Netty-如何写一个Http服务器

思路

下文说的Handler就是Controller

  1. 先说说为什么依赖SpringBoot,因为代码抄的是Spring-MVC的,而且SpringBoot提供的容器API真的很方便。
  2. 调度器:作为一个业务框架,需要业务逻辑对底层的依赖,也就是要满足大部分业务处理不会用到Netty的API,也就是Netty专注于接收和响应HTTP请求
  3. HandlerMapping:存储了请求路径与HandlerMethod的关系,可以通过uri定位到对应的Controller的方法
  4. HandlerMethod:存储了Controller和业务处理方法的信息
  5. ArgumentResolver:负责将请求参数转换成业务处理方法所需的参数(PS:Spring的DataBinder是个好东西)
  6. ReturnValueHandler:负责返回值的转换,比如将Map转成JSON

实现

实现有太多东西讲了,文章讲不完。我抄了一个简化版的Spring-MVC,就在文章开头的项目地址,感兴趣的自行去看吧。

Original: https://www.cnblogs.com/konghuanxi/p/16396875.html
Author: 王谷雨
Title: Netty-如何写一个Http服务器

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/583258/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • redis后台启动

    打开redis.conf文件 把daemonize设置为yes posted @2021-11-25 15:30 HongMaJu 阅读(73 ) 评论() 编辑 Original…

    Linux 2023年5月28日
    084
  • 微信公众号开发之获取微信用户的openID

    (注:openID同一用户同一应用唯一,UnionID同一用户不同应用唯一。不同应用指微信开放平台下的不同用户。) 1、 申请测试号(获得appID、appsecret) 2、 填…

    Linux 2023年6月13日
    076
  • Running powershell scripts during nuget package installation and removal

    来源:https://devblogs.microsoft.com/nuget/NuGet-3-What-and-Why/ Since Visual Studio 2015 was…

    Linux 2023年5月28日
    095
  • python爬虫爬取国家科技报告服务系统数据,共计30余万条

    python爬虫爬取国家科技报告服务系统数据,共计30余万条 按学科分类【中图分类】 共计三十余万条科技报告数据 爬取的网址:https://www.nstrs.cn/kjbg/n…

    Linux 2023年6月14日
    073
  • Apache Shiro反序列化漏洞(Shiro550)

    1.漏洞原理: Shiro 是 Java 的一个安全框架,执行身份验证、授权、密码、会话管理 shiro默认使用了CookieRememberMeManager,其处理cookie…

    Linux 2023年6月13日
    069
  • sql server的简单分页

    — 显示前条数据 select top(4) * from students; –pageSize: 每页显示的条数 –pageNow: 当前页…

    Linux 2023年6月7日
    0109
  • [LINUX] Arch Linux 硬盘拷贝式装系统+新增 home 分区

    前言 1. 实操 1.1 整个磁盘拷贝 1.2 创建 home 分区 1.3 修改 fstab 实现自动挂载 2. 涉及到的知识点 2.1 fstab 2.2 dd 命令 2.3 …

    Linux 2023年5月27日
    0176
  • shell练习:svndiff & change_ip

    在shell中写代码,有些命令比较常用,所以整合到一起来实现一些小功能,笔记一下: 第一个脚本是用来对比SVN中的不同版本的不同,借助了svn自身的命令行工具和vimdiff工具:…

    Linux 2023年5月28日
    090
  • MySQL-创建表

    如何在指定数据库中创建表 我们先来了解一下在数据库中创建表的规则: CREATE TABLE 表名 ( 字段名,数据类型, 字段名,数据类型, ….. ) 例如: 添加…

    Linux 2023年6月8日
    0114
  • 使用idea操作git(ssh协议)

    问题 我们发现,使用IDEA上的git功能,当使用ssh协议出现了可以commit但无法push和pull的问题,经过测试发现原因是Could not read from rems…

    Linux 2023年6月6日
    0122
  • [转]Redis cluster failover

    今天测试了redis cluster failover 功能,在切换过程中很快,但在failover时有force 与takeover 之分 [RHZYTEST_10:REDIS:…

    Linux 2023年5月28日
    086
  • 数据结构 二叉树

    cpp;gutter:true;</p> <h1>include</h1> <p>using namespace std;</…

    Linux 2023年6月13日
    068
  • Jstack排查线上CPU100%

    Jstack排查线上CPU100% 介绍 jstack是JVM自带的Java堆栈跟踪工具,用于生成java虚拟机当前时刻的线程快照,来帮助定位线程出现长时间停顿的原因,例如死锁、死…

    Linux 2023年6月6日
    095
  • 良许跌宕起伏的2021年

    大家好,我是良许,前码农,现创业者。 时间飞逝,转眼就2021年年尾了,向各位「股东」们汇报一下良许的 2021 年。 公众号运营至今,每年我都会写年终总结,前三次总结给大家放在下…

    Linux 2023年6月14日
    0110
  • Golang中字符串、数组、切片排序

    使用Golang的sort包用来排序,包括二分查找等操作。下面通过实例代码来分享下sort包的使用技巧: 使用接口排序: sort.Sort(data Interface) 自定义…

    Linux 2023年6月6日
    0103
  • Spring中毒太深,离开了Spring,我居然连最基本的接口都不会写了¯_(ツ)_/¯

    前言 众所周知,Java必学的框架其中就是SSM,Spring已经融入了每个开发人员的生活,成为了不可或缺的一份子。 随着 Spring 的崛起以及其功能的完善,现在可能绝大部分项…

    Linux 2023年6月13日
    078
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球