基於netty http協議棧的輕量級流程式控制制組件的實現

來源:互聯網
上載者:User

標籤:imap   github   技術分享   cat   java   arp   pcl   產生   擴充   

今兒個是冬至,所謂“冬大過年”,公司也應景五點鐘就放大伙兒回家吃餃子喝羊肉湯了,而我本著極高的職業素養依然堅持留在公司(實則因為沒餃子吃沒羊肉湯喝,只能呆公司吃食堂……)。趁著這一個多小時的時間,想跟大家介紹下前段時間整的一個基於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)。

基礎資料型別 (Elementary Data Type)解析

這個例子示範了從 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:123server output names:aaaabbbb

 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=cyfonlyproject=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協議棧的輕量級流程式控制制組件的實現

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.