今儿个是冬至,所谓“冬大过年”,公司也应景五点钟就放大伙儿回家吃饺子喝羊肉汤了,而我本着极高的职业素养依然坚持留在公司(实则因为没饺子吃没羊肉汤喝,只能呆公司吃食堂……)。趁着这一个多小时的时间,想跟大家介绍下前段时间整的一个基于netty http协议栈的轻量级流程控制组件 nettice(点此查看代码),目前已经实现了一些功能并将持续完善,希望能为大伙儿切实解决一点开发问题(或者至少提供一些思路)。

什么是流程控制组件?

服务的流程,简单来说就是在一次交互过程中,对 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:

<?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:

.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 映射:

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]");
  }
}
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 中设置:

<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 中获取基本类型的方法,如果没有值会自动设置默认值。

客户端请求:

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:

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

输出结果:

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

List/Array 类型解析

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

客户端请求:

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:

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()]");
    return new Render(RenderType.JSON, obj.toJSONString());
}

输出结果:

server output ids:
1
2
3
server output names:
aaaa
bbbb

Map 类型解析

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

客户端代码:

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:

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

输出结果:

server output srcmap:
author=cyfonly
project=nettice

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

渲染数据

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

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

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 协议栈开发 http server 的一些痛点。

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

希望有兴趣的童鞋可以仔细研读代码,若有更好的想法欢迎通过评论或者加本人QQ(869827095)私下交流,或者和本人一起编码实现,都是非常欢迎的。

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

  1. 基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇

    基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇 前提 最近对网络编程方面比较有兴趣,在微服务实践上也用到了相对主流的RPC框架如Spring Cloud Gateway底层也切换 ...

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

    前提 前置文章: Github Page:<基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇> Coding Page:<基于Netty和SpringBoot实现 ...

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

    前提 前置文章: <基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇> <基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇> 前 ...

  4. 基于Netty和SpringBoot实现一个轻量级RPC框架-Client端请求响应同步化处理

    前提 前置文章: <基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇> <基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇> & ...

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

    基于Netty的私有协议栈的开发 书是人类进步的阶梯,每读一本书都使自己得以提升,以前看书都是看了就看了,当时感觉受益匪浅,时间一长就又还回到书本了!所以说,好记性不如烂笔头,以后每次看完一本书都写一 ...

  6. 适合新手:从零开发一个IM服务端(基于Netty,有完整源码)

    本文由“yuanrw”分享,博客:juejin.im/user/5cefab8451882510eb758606,收录时内容有改动和修订. 0.引言 站长提示:本文适合IM新手阅读,但最好有一定的网络 ...

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

    自从在园子里,发表了两篇如何基于Netty构建RPC服务器的文章:谈谈如何使用Netty开发实现高性能的RPC服务器.Netty实现高性能RPC服务器优化篇之消息序列化 之后,收到了很多同行.园友们热 ...

  8. 基于netty轻量的高性能分布式RPC服务框架forest<上篇>

    工作几年,用过不不少RPC框架,也算是读过一些RPC源码.之前也撸过几次RPC框架,但是不断的被自己否定,最近终于又撸了一个,希望能够不断迭代出自己喜欢的样子. 顺便也记录一下撸RPC的过程,一来作为 ...

  9. YbSoftwareFactory 代码生成插件【十六】:Web 下灵活、强大的审批流程实现(含流程控制组件、流程设计器和表单设计器)

    程序=数据结构+算法,而企业级的软件=数据+流程,流程往往千差万别,客户自身有时都搞不清楚,随时变化的情况更是家常便饭,抛开功能等不谈,需求变化很大程度上就是流程的变化,流程的变化会给开发工作造成很大 ...

随机推荐

  1. PHP-----文件系统的交互

    本文讲解php中于文件交互中所使用的函数 代码示例 <html> <head> <title> File Detail </title> </he ...

  2. css居中div的几种常用方法

    在开发过程中,很多需求需要我们居中一个div,比如html文档流当中的一块div,比如弹出层内容部分这种脱离了文档流等.不同的情况有不同的居中方式,接下来就分享下一下几种常用的居中方式. 1.text ...

  3. Struts2实现ajax的两种方式

    基于Struts2框架下实现Ajax有两种方式,第一种是原声的方式,另外一种是struts2自带的一个插件. js部分调用方式是一样的: JS代码: function testAjax() { var ...

  4. 通过AngularJS实现前端与后台的数据对接(一)——预备工作篇

    最近,笔者在做一个项目:使用AngularJS,从而实现前端与后台的数据对接.笔者这是第一次做前端与后台的数据对接的工作,因此遇到了许多问题.笔者在这些问题中,总结了一些如何实现前端与后台的数据对接的 ...

  5. 原生JS实现-星级评分系统

    今天我又写了个很酷的实例:星级评分系统(可自定义星星个数.显示信息) sufuStar.star();使用默认值5个星星,默认信息 var msg = [........]; sufuStar.sta ...

  6. ionic第一坑——ion-slide-box坑(ion-slide分两页的坑)

    ionic.views.Slider = ionic.views.View.inherit({ initialize: function (options) { . . . function setu ...

  7. 海鑫智圣:物联网漫谈之MQTT协议

    什么是MQTT协议 MQTT(消息队列遥测传输协议)是IBM在1999年专门针对物联网等应用场景来制订的轻量级双向消息传输协议,它主要是为了解决物联网上使用到的设备的互相通信的问题,以及这些设备与后端 ...

  8. mono for android 获取手机照片或拍照并裁剪保存

    axml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android ...

  9. 在 Linux 上使用 Jexus + Mono 建立 Asp.Net 网站.

    godaddy 买了个net空间,一点也不好用. 几个G的数据, 上传数据只有几kb , 想用 ssh 登录上去用 wget 下载,也不行 windows的主机貌似没有 ssh 功能... 后来实在忍 ...

  10. 【腾讯优测干货分享】Android内存泄漏的简单检查与分析方法

    本文来自于Dev Club 开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57d14047603a5bf1242ad01b 导语 内存泄漏问题大约是An ...