程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 基於netty http協議棧的輕量級流程控制組件的實現,netty協議棧

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

編輯:JAVA綜合教程

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


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

 消息分發的整體設計如下(一圖勝千言):

如何使用 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)私下交流,或者和本人一起編碼實現,都是非常歡迎的。

 

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved