基于netty http协议栈的轻量级流程控制组件的实现

什么是流程控制组件?

服务的流程,简单来说就是在一次交互过程中,对 client 端而言,是从请求的组装、发送,再到响应的接收、解析和业务处理的一个顺序流;对 server 端而言,是从请求的接收、解析和业务处理,再到响应的组装、发送的一个顺序流。而本文所说的流程控制组件,指的是在使用 netty http 协议栈开发 http server 的过程中,保证流程按照该顺序流执行,同时抽象出通用的非业务逻辑并对上层透明,使开发人员只需关注业务逻辑的底层实现。

为什么需要这么一个组件?

一个 http server 往往需要处理多种业务逻辑,每一个业务逻辑都对应着一个请求消息和一个响应消息,服务端需要把这些不同的消息自动分发到对应的业务逻辑中处理。

然而使用 netty http 协议栈开发过 http server 的童鞋都应该有所了解,netty 并没有提供消息分发组件。

这种情况下只能通过请求消息中的某个特殊标识(如某个字段值)来区分业务,使用 switch case 来处理。但这种方式下,随着业务逻辑的增多,switch case 代码块将越来越长,大大影响代码可读性;并且每次新增、删除业务逻辑时,都需要修改这段逻辑代码,后期维护也越来越麻烦。

此外,使用 netty http 协议栈时,并没有提供客户端 parameter 到服务端业务 method 入参的直接解析和映射。

这句话是什么意思呢?举个栗子,你在客户端使用 httpclient 给 netty http 服务端发送了一个消息,传递参数为“project=nettice&author=cyfonly”,而服务端有个业务方法 public void bizHandle(String project, String author),那么在调用 bizHandle 这个方法前,你肯定得先手动写代码解析客户端的请求参数解析出 project 和 author 两个 key 对应的 value。

那么问题来了,当业务逻辑越来越多,针对每个业务逻辑的请求,你都不得不单独写一段参数解析的代码。这是多么X疼的一件事情啊,而且后面还有一大堆业务逻辑代码要写呢!

有没有办法可以避免通过写 switch case 代码段来分发请求,并且使用统一方法来解析所有的请求参数呢?

当然有,nettice 就是为解决这个而诞生的啦~~

nettice 到底能做些什么呢?

特性

  • 接收装配请求数据、流程控制和渲染数据
  • URI 到方法直接映射,以及命名空间

功能

  • 对 HttpRequest 的流程控制
  • 像普通方法一样处理 http 请求
  • 对请求的数据自动装配,支持基本类型、List、Array 和 Map
  • 提供 Render 方法渲染并写回响应,支持多种 Content-Type
  • 支持可配置的命名空间

nettice 是如何设计并实现的呢?

消息分发的整体设计如下(一图胜千言):

Action请求处理如下(一图胜千言+1):

如何使用 nettice?

nettice 引入项目

nettice 作为一个组件使用起来时很简单,此处使用具体的栗子来说明(demo代码请点此查看)。

首先是引入 nettice-core.jar,或者直接使用 nettice-core 源码作为 maven 项目的 module(目前没有上传到 maven 仓库,暂时没法通过 pom 依赖来引入)。然后定义 nettice 组件的必要配置 nettice.xml:


1

2

3

4

5

6

7

8

9

10

11

<?xml version="1.0" encoding="UTF-8"?>

<router>

    <action-package>

        <package>com.server.action</package>

    </action-package>

    <namespaces>

        <!--按包分配命名空间,多个匹配项时,采用目录级别最多的-->

        <namespace name="/nettp/" packages="com.server.action.*"></namespace>

        <namespace name="/nettp/sub/" packages="com.server.action.sub"></namespace>

    </namespaces>

</router>

最后在服务端中添加消息分发handler:


1

.addLast("dispatcher",new ActionDispatcher())

好了,现在就可以使用 nettice 的功能啦!

特别注意,业务处理类需继承 BaseAction 才能被 nettice 组件识别!

URI 映射和命名空间

使用方法名作为 URI 映射关键字,如果项目中存在同样名字的方法会产生冲突,开发者可以使用 @Namespaces 注解或者在 nettice.xml 配置中添加 namespaces 来修改 URI 映射,以规避此问题。

例如 com.server.action.DemoAction 提供了 returnTextUseNamespace() 方法,com.server.action.sub.SubDemoAction 也提供了 returnTextUseNamespace() 方法,但两个方法实现不同功能。nettice 组件默认使用方法名进行 URI 映射,那么上述两个 returnTextUseNamespace() 方法会产生冲突,开发者可以使用 @Namespace 注解修改 URI 映射:


1

2

3

4

5

6

7

8

package com.server.action;

public class DemoAction extends BaseAction{

      @Namespace("/nettp/demo/")

      public Render returnTextUseNamespace(@Read(key="id") Integer id, @Read(key="project") String project){

              //do something

              return new Render(RenderType.TEXT, "returnTextUseNamespace in [DemoAction]");

      }

}


1

2

3

4

5

6

7

8

package com.server.action.sub;

public class SubDemoAction extends BaseAction{

      @Namespace("/nettp/subdemo/")

      public Render returnTextUseNamespace(@Read(key="ids") Integer[] ids, @Read(key="names") List<String> names){

            //do something

            return new Render(RenderType.TEXT, "returnTextUseNamespace in [SubDemoAction]");

      }

}

也可以在 nettice.xml 中设置:


1

2

3

4

<namespaces>

    <namespace name="/nettp/demo/" packages="com.server.action.*"></namespace>

    <namespace name="/nettp/subdemo/" packages="com.server.action.sub"></namespace>

</namespaces>

接收装配请求数据

使用 @Read 注解可以自动装配请求数组,支持不同的类型(基本类型、List、Array 和 Map),可以设置默认值(目前仅支持基本类型设置 defaultValue)。

基本数据类型解析

这个例子演示了从 HttpRequest 中获取基本类型的方法,如果没有值会自动设置默认值。

客户端请求:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

private static void sendGetPriType() throws Exception{

    String path = "http://127.0.0.1:8080/nettp/primTypeTest.action?";

    String getUrl = path + "id=10001&project=nettice&author=cyfonly";

    java.net.URL url = new java.net.URL(getUrl);

    java.net.HttpURLConnection conn = (java.net.HttpURLConnection) url.openConnection();

    conn.setRequestMethod("GET");

    conn.setDoOutput(true);

    conn.connect();

    if(conn.getResponseCode() == 200){

        BufferedReader in new BufferedReader(new InputStreamReader((InputStream) conn.getInputStream(), "UTF-8"));

        String msg = in.readLine();

        System.out.println("msg: " + msg);

        in.close();

    }

    conn.disconnect();

}

服务端 method:


1

2

3

4

public Render primTypeTest(@Read(key="id", defaultValue="1" ) Integer id, @Read(key="project") String project, @Read(key="author") String author){

    System.out.println("Receive parameters: id=" + id + ",project=" + project + ",author=" + author);

    return new Render(RenderType.TEXT, "Received your primTypeTest request.[from primTypeTest]");

}

输出结果:


1

Receive parameters: id=10001,project=nettice,author=cyfonly

List/Array 类型解析

这个例子演示了从 HttpRequest 中获取 List/Array 类型的方法。

客户端请求:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

private static void sendPostJsonArrayAndList() throws Exception{

    String path = "http://127.0.0.1:8080/nettp/sub/arrayListTypeTest.action";

    JSONObject obj = new JSONObject();

    int[] ids = {1,2,3};

    List<String> names = new ArrayList<String>();

    names.add("aaaa");

    names.add("bbbb");

    obj.put("ids", ids);

    obj.put("names", names);

    String jsonStr = obj.toJSONString();

    byte[] data = jsonStr.getBytes();

    java.net.URL url = new java.net.URL(path);

    java.net.HttpURLConnection conn = (java.net.HttpURLConnection) url.openConnection();

    conn.setRequestMethod("POST");

    conn.setDoOutput(true);

    conn.setRequestProperty("Content-Type""application/json;charset=UTF-8");

    conn.setRequestProperty("Content-Length", String.valueOf(data.length));

    OutputStream outStream = conn.getOutputStream();

    outStream.write(data);

    outStream.flush();

    outStream.close();

    if(conn.getResponseCode() == 200){

        BufferedReader in new BufferedReader(new InputStreamReader((InputStream) conn.getInputStream(), "UTF-8"));

        String msg = in.readLine();

        System.out.println("msg: " + msg);

        in.close();

    }

    conn.disconnect();

}

服务端 method:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

public Render arrayListTypeTest(@Read(key="ids") Integer[] ids, @Read(key="names") List<String> names){

    System.out.println("server output ids:");

    for(int i=0; i<ids.length; i++){

        System.out.println(ids[i]);

    }

    System.out.println("server output names:");

    for(String item : names){

        System.out.println(item);

    }

    JSONObject obj = new JSONObject();

    obj.put("code", 0);

    obj.put("msg""Received your Array/List request.[from arrayListTypeTest()]");<br>

    return new Render(RenderType.JSON, obj.toJSONString());

}

输出结果:


1

2

3

4

5

6

7

server output ids:

1

2

3

server output names:

aaaa

bbbb

Map 类型解析

这个例子演示了从 HttpRequest 中获取 Map 类型的方法。

客户端代码:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

private static void sendPostJsonMap() throws Exception{

    String path = "http://127.0.0.1:8080/nettp/sub/mapTypeTest.action";

    JSONObject obj = new JSONObject();

    Map<String, String> srcmap = new HashMap<String, String>();

    srcmap.put("project""nettice");

    srcmap.put("author""cyfonly");

    obj.put("srcmap", srcmap);

    String jsonStr = obj.toJSONString();

    byte[] data = jsonStr.getBytes();

    java.net.URL url = new java.net.URL(path);

    java.net.HttpURLConnection conn = (java.net.HttpURLConnection) url.openConnection();

    conn.setRequestMethod("POST");

    conn.setDoOutput(true);

    conn.setRequestProperty("Content-Type""application/json;charset=UTF-8");

    conn.setRequestProperty("Content-Length", String.valueOf(data.length));

    OutputStream outStream = conn.getOutputStream();

    outStream.write(data);

    outStream.flush();

    outStream.close();

    if(conn.getResponseCode() == 200){

        BufferedReader in new BufferedReader(new InputStreamReader((InputStream) conn.getInputStream(), "UTF-8"));

        String msg = in.readLine();

        System.out.println("msg: " + msg);

        in.close();

    }

    conn.disconnect();

}

服务端 method:


1

2

3

4

5

6

7

8

9

10

11

public Render mapTypeTest(@Read(key="srcmap") Map<String,String> srcmap){

    System.out.println("server output srcmap:");

    for(String key : srcmap.keySet()){

        System.out.println(key + "=" + srcmap.get(key));

    }

    JSONObject obj = new JSONObject();

    obj.put("code", 0);

    obj.put("msg""Received your Map request.[from mapTypeTest]");

    return new Render(RenderType.JSON, obj.toJSONString(www.10ci.com));

}

输出结果:


1

2

3

server output srcmap:

author=cyfonly

project=nettice

注意,使用 Map 时限定了只能存在一个 Map 参数。

渲染数据

处理方法可以通过返回 Render 对象向客户端返回特定格式的数据,一个 Rendersbyigouyule.cn 对象由枚举类型 RenderType 和 data 两部分组成。nettice 组件会通过 RenderType 来为 Response 设置合适的 Content-Type,开发者也可以扩展 Render 以及相关类来实现更多的类型支持。

例如这是一个返回 JSON 对象的例子,客户端将收到一个 Json 对象:


1

2

3

4

5

6

7

public Render postPriMap(){

    JSONObject obj = new JSONObject();

    obj.put("code", 0);

    obj.put("msg""had received your request.");

    return new Render(RenderType.JSON, obj.toJSONString());

}

接下来还会完善哪些?

正如开头说的那样,目前 nettice 实现了部分功能,在性能上也暂时没有太多的时间做优化,所以后续肯定会继续完善。目前有计划做的事情如下:

  • java bean 支持
  • 参数解析流程优化
  • 性能优化

但就目前而言,nettice 确实解决了使用 netty http 协议栈开发sbuarencai.cn http server 的一些痛点。

好了,晚餐时间到,暂时先介绍这么多。如有未介绍到或者介绍不够详细的,将会完善本文,请持续关注~~

时间: 2024-10-17 02:13:30

基于netty http协议栈的轻量级流程控制组件的实现的相关文章

基于Netty实现的RESTful框架--netty-rest-server

在工作中用Netty做了几个服务,感觉Netty做出来的程序性能好,资源占用少,但是实现Http服务比较麻烦,于是就参考Spring MVC的注解基于Netty实现了一个轻量级的RESTful框架. 该框架提供了控制器注解.全局异常控制器.拦截器等功能. 注解名称参考了Spring MVC,编译理解和记忆,主要包括如下注解: @RestController @RequestMapping @GetMapping @PostMapping @DeleteMapping @PutMapping @P

基于Netty和SpringBoot实现一个轻量级RPC框架-Client篇

前提 前置文章: <基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇> <基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇> 前一篇文章相对简略地介绍了RPC服务端的编写,而这篇博文最要介绍服务端(Client)的实现.RPC调用一般是面向契约编程的,而Client的核心功能就是:把契约接口方法的调用抽象为使用Netty向RPC服务端通过私有协议发送一个请求.这里最底层的实现依赖于动态代理,因此动态代理是动态实现接口的最简单方式(如果

基于Netty的私有协议栈的开发

基于Netty的私有协议栈的开发 书是人类进步的阶梯,每读一本书都使自己得以提升,以前看书都是看了就看了,当时感觉受益匪浅,时间一长就又还回到书本了!所以说,好记性不如烂笔头,以后每次看完一本书都写一些读后感,对于技术书则把对让自己醍醐灌顶的篇章记录下来,以便以后翻阅查看,也是记录自己学习的过程- _ -. OK!言归正传,最近由于公司需要做一个网关项目,需要用到基于TCP/IP私有协议接收数据,看完了<Netty权威指南>这本书,感觉作者写的很好,有些地方让我获益良多,虽然书上有些例子跑不通

基于Netty的聊天系统(一)通讯原理篇

今天周六,正好顺便把聊天系统的通讯原理写一下,本来是用XMPP+Openfire做了一个聊天,但是在做群聊那块需要去写插件来主动向表里变去写数据,因为openfire外国人写的,最初设计的群聊是会议室那种形式,和我们现在这种QQ群聊还是有差别的,改造起来比较麻烦,需要去通都源码等等,openfire是基于mina来写的,mina和netty又出自同一作者之手,那么我们就基于netty来写一个吧,首先我们来谈一谈通讯的原理 1:通讯原理 首先比方说A和B两个人要进行聊天(这里我们先讨论一对一这种聊

基于Netty打造RPC服务器设计经验谈

自从在园子里,发表了两篇如何基于Netty构建RPC服务器的文章:谈谈如何使用Netty开发实现高性能的RPC服务器.Netty实现高性能RPC服务器优化篇之消息序列化 之后,收到了很多同行.园友们热情的反馈和若干个优化建议,于是利用闲暇时间,打算对原来NettyRPC中不合理的模块进行重构,并且增强了一些特性,主要的优化点如下: 在原来编码解码器:JDK原生的对象序列化方式.kryo.hessian,新增了:protostuff. 优化了NettyRPC服务端的线程池模型,支持LinkedBl

基于Netty的聊天系统(二)协议定制----登录篇

上一篇文章我们讨论了聊天的基本流程,那么我们现在基于上一篇文章的流程开始定义协议,如果有朋友有更好的建议,可以在下边回复一起学习讨论,我们说登录分为两部分,第一部分为和服务器的连接阶段,第二部分为验证阶段,那么首先我们基于这2个部分来指定协议: 连接阶段: {"id":"xxxx","#":"conn","u":1[email protected]/ios,"v":100} id:客户端

基于Netty的Redis客户端-Nedis

最近温习了一遍Redis命令,忧伤的是很多东西已交还给老师,正好赶上antirez大神在愚人节发布了Redis 3.0,Redis终于有了支持集群的正式版本,于是心血来潮决定自己实现一个Redis客户端来抚慰我这颗忧伤的心灵. Jedis已经足够强大,它的网络连接是基于阻塞式IO,实现非常简单易懂,但是OIO和NIO相比性能上有劣势,于是决定通过NIO来实现和Redis服务器的网络连接,现在业界最优秀的NIO框架非Netty莫属了,正好以前也学过Netty框架,所以决定基于Netty来实现这个R

基于Netty构建高性能的部标808协议的GPS服务器

使用Java语言开发一个高质量和高性能的jt808 协议的GPS通信服务器,并不是一件简单容易的事情,开发出来一段程序和能够承受数十万台车载接入是两码事,除去开发部标808协议的固有复杂性和几个月长周期的协议Bug调试,作为大批量794车载终端接入的服务端,需要能够处理网络的闪断.客户端的重连.安全认证和消息的编解码.半包处理等.如果没有足够的网络编程经验积累和深入了解部标808协议文档,自研的GPS服务器往往需要半年甚至数年的时间才能最终稳定下来,这种成本即便对一个大公司而言也是个严重的挑战.

基于netty轻量的高性能分布式RPC服务框架forest&lt;下篇&gt;

基于netty轻量的高性能分布式RPC服务框架forest<上篇> 文章已经简单介绍了forest的快速入门,本文旨在介绍forest用户指南. 基本介绍 Forest是一套基于java开发的RPC框架,除了常规的点对点调用外,Motan还提供服务治理功能,包括服务节点的自动发现.摘除.高可用和负载均衡等. 架构概述 Forest中分为服务提供方(RPC Server),服务调用方(RPC Client)和服务注册中心(Registry)三个角色. Server提供服务,向Registry注册