維's profileIT : 是工作還是嗜好?PhotosBlogListsMore Tools Help

Blog


    September 28

    JSON 程式設計 – 使用過濾器

    在以前Tiburon遊記的文章解釋過JSON是使用字串型態來傳遞資料的,因此所有其他型態的資料都必須轉換為字串的型態,雖然如此一來在處理上比較簡單,但這也造成了其他的問題,例如一些敏感性的資料如果使用字串型態來傳遞的話就會有問題。DataSnap 2010為了解決這種問題因此加入了過濾器的機制,讓開發人員在傳遞特殊的資料時可以藉由過濾器來進行額外的處理,例如在傳遞資料出去時先加密,並且在接受到資料之後再進行解密。本篇文章的目的即在於討論如何使用DataSnap 2010的過濾器。

    使用DataSnap 2010的過濾器非常的簡單,Delphi 2010也內建了一個壓縮過濾器,可以有效的壓縮使用TCP/IP通訊協定的資料傳遞。讓我們先說明如何使用這個內建的過濾器,稍後我們再深入的說明如何開發客製化過濾器。

    讓我們仍然使用上篇文章的範例,要使用過濾器,請開啟ServerContainerUnit程式單元,點選表單中的TDSTCPServerTransport元件,並且雙擊它的Filters特性,此時Filters特性值編輯器會啟動,請於其中加入一個新的過濾器,點選此新的過濾器,然後在物件檢視器中選擇它的FilterId為ZlibCompression,如下所示:

    clip_image002

    ZlibCompression過濾器就是DataSnap 2010內建的壓縮過濾器,在加入了ZlibCompression過濾器之後,編譯並且執行範例DataSnap伺服器,現在DataSnap伺服器就提供了壓縮JSON資料的能力。

    現在再讓我們開啟用戶端應用程式,因為我們要在用戶端應用程式中加入解壓縮資料的能力,這個非常的簡單,我們只要在用戶端應用程式的主表單中加入使用DBXCompressionFilter程式單元即可,例如下面就是用戶端應用程式加入DBXCompressionFilter程式單元的程式碼:

    implementation

    uses DBXJSONReflect, DBXJSON, uServerProxy, uEmployee, DBXCompressionFilter;

    現在編譯並且執行用戶端應用程式,並且讓我們使用TCP Viewer來觀察使用壓縮過濾器之前的情形以及使用壓縮過濾器之後的效果。

    下圖是TCP Viewer顯示範例DataSnap應用系統使用壓縮過濾器之前的情形,從下圖中我們可以看到在DataSnap伺服器和用戶端應用程式之間傳遞的資料當然是使用字串的型態,所有傳遞的資料都一清二楚,同時請讀者注意下圖右邊顯示了從伺服器傳遞到用戶端的資料量(938位元組)以及從用戶端傳遞到伺服端的資料量(706位元組)。

    clip_image004

    而下圖則是使用壓縮過濾器之後的效果:

    clip_image006

    從上圖中可以看到傳遞的資料經過壓縮,因此不易看出原始的資料,而且請讀者注意下圖右邊顯示了從伺服器傳遞到用戶端的資料量(653位元組)以及從用戶端傳遞到伺服端的資料量(602位元組),可見到壓縮過濾器有效的減少了伺服器和用戶端之間的資料傳遞量,這不但可以增加分散式應用程式的執行速度,也可以增加支援的用戶端的數量。

    如何? 使用過濾器是不是又簡單,又有明顯的效果? 不過DataSnap 2010只提供了一個內建的過濾器實在太少,好在DataSnap 2010過濾器架構在設計時就考慮到了允許讓開發人員能夠自行開發過濾器並且內嵌到DataSnap之中,接下來筆者將討論如何開發客製化過濾器並且使用在DataSnap 2010的分散式應用系統中。

    開發客製化過濾器

    要開發客製化過濾器,開發人員必須從TTransportFilter類別衍生子代類別並且實作TTransportFilter類別中相關的虛擬方法,下面的表單說明了開發人員需要實作的虛擬方法:

    函式名稱

    回傳型態

    說明

    GetParameters

    TDBXStringArray

    回傳所有的參數

    GetUserParameters

    TDBXStringArray

    回傳使用者可改變的參數

    ProcessInput

    TBytes

    使用客製化程式碼正向處理傳遞的資料流

    ProcessOutput

    TBytes

    使用客製化程式碼反向處理傳遞的資料流

    Id

    UnicodeString

    過濾器的ID

    GetParameterValue

    UnicodeString

    取得特定名稱的參數值

    SetParameterValue

    Boolean

    設定特定名稱的參數值

    瞭解了需要實作那些虛擬方法之後,我們就可以開始動手開發一個客製化過濾器了。在本文中筆者將撰寫一個非常簡單的加密/解密過濾器,其實這個加密/解密過濾器只是在傳遞資料時和一個字串進行xor的動作,到了另一端再次xor相同的字串而已。

    首先讓我們宣告範例TTransportEncryptFilter類別從TTransportFilter類別繼承下來並且複載相關必要的虛擬方法:

    TTransportEncryptFilter = class(TTransportFilter)

    private

    FEncrypt: TSimpleEncryptor;

    FParameters: TDictionary<String, String>;

    protected

    function GetParameters: TDBXStringArray; override;

    function GetUserParameters: TDBXStringArray; override;

    public

    function GetParameterValue(const ParamName: UnicodeString): UnicodeString;

    override;

    function SetParameterValue(const ParamName: UnicodeString;

    const ParamValue: UnicodeString): Boolean; override;

    constructor Create; override;

    destructor Destroy; override;

    function ProcessInput(const Data: TBytes): TBytes; override;

    function ProcessOutput(const Data: TBytes): TBytes; override;

    function Id: UnicodeString; override;

    end;

    TTransportEncryptFilter類別將使用TSimpleEncryptor進行字串xor的運算,在TSimpleEncryptor類別中實作了兩個方法,Encrypt和Decrypt,其實這兩個方法的實作程式碼是一樣的,只是為了說明方便分別實作成Encrypt和Decrypt以便讓讀者瞭解。Encrypt和Decrypt接受TBytes型態的參數,這個參數在Encrypt方法中是代表傳遞出去的資料,Encrypt方法使用程式碼加密之後再把加密過的資料以TBytes型態回傳。

    而Decrypt的參數則是代表接受來的資料,Decrypt方法必須使用程式碼加以解密以還原資料。

    TSimpleEncryptor = class

    protected

    public

    function Encrypt(const Data: TBytes): TBytes;

    function Decrypt(const Data: TBytes): TBytes;

    constructor Create;

    end;

    下面是這兩個方法的實作程式碼,讀者可以看到這兩個方法的實作程式碼是 樣的,它們都接受的參數以'DexterHighlanderTiburonWeaver'這個鍵值字串進行xor的運算:

    const

    EncryptKey = 'DexterHighlanderTiburonWeaver';

    constructor TSimpleEncryptor.Create;

    begin

    inherited Create;

    end;

    function TSimpleEncryptor.Decrypt(const Data: TBytes): TBytes;

    var

    i: Integer;

    idx: Integer;

    begin

    Result := Data;

    idx := 0;

    for i := 0 to Length(Data) - 1 do

    begin

    Result[i] := Byte(Chr(Ord(Data[i]) xor Ord(EncryptKey[idx])));

    Inc(idx);

    if (idx > Length(EncryptKey)) then

    idx := 0;

    end;

    end;

    function TSimpleEncryptor.Encrypt(const Data: TBytes): TBytes;

    var

    i: Integer;

    idx: Integer;

    begin

    Result := Data;

    idx := 0;

    for i := 0 to Length(Data) - 1 do

    begin

    Result[i] := Byte(Chr(Ord(Data[i]) xor Ord(EncryptKey[idx])));

    Inc(idx);

    if (idx > Length(EncryptKey)) then

    idx := 0;

    end;

    end;

    下面則是TTransportEncryptFilter類別的實作程式碼,讀者可以看到在在建構函式中建立了TSimpleEncryptor物件,並且分別在ProcessInput虛擬方法中呼叫TSimpleEncryptor物件的Encrypt方法加密傳遞的資料並且在ProcessOutput虛擬方法中呼叫TSimpleEncryptor物件的Decrypt方法以解密資料:

    function TTransportEncryptFilter.GetUserParameters: TDBXStringArray;

    begin

    SetLength(Result, 1);

    Result[0] := EncryptKey;

    end;

    function TTransportEncryptFilter.GetParameters: TDBXStringArray;

    begin

    SetLength(Result, 1);

    Result[0] := EncryptKey;

    end;

    function TTransportEncryptFilter.GetParameterValue

    (const ParamName: UnicodeString): UnicodeString;

    begin

    FParameters.TryGetValue(ParamName, Result);

    if ( ParamName = EncryptKey ) and ( Result = '' ) then

    Result := '0';

    end;

    function TTransportEncryptFilter.SetParameterValue

    (const ParamName, ParamValue: UnicodeString): Boolean;

    begin

    FParameters.AddOrSetValue(ParamName, ParamValue);

    Result := True;

    end;

    constructor TTransportEncryptFilter.Create;

    begin

    inherited Create;

    FParameters := TDictionary<String, String>.Create;

    FEncrypt := TSimpleEncryptor.Create;

    end;

    destructor TTransportEncryptFilter.Destroy;

    begin

    FreeAndNil(FParameters);

    FreeAndNil(FEncrypt);

    inherited Destroy;

    end;

    function TTransportEncryptFilter.ProcessInput(const Data: TBytes): TBytes;

    begin

    OutputDebugString(PWideChar('Encrypted - ' + Stringof(Data)));

    Result := FEncrypt.Encrypt(Data);

    end;

    function TTransportEncryptFilter.ProcessOutput(const Data: TBytes): TBytes;

    begin

    OutputDebugString(PWideChar('Decrypted - ' + Stringof(Data)));

    Result := FEncrypt.Decrypt(Data);

    end;

    function TTransportEncryptFilter.Id: UnicodeString;

    begin

    Result := EncryptFilterName;

    end;

    最後我們需要在initialization部份註冊這個客製化過濾器並且在finalization部份解除註冊客製化過濾器:

    initialization

    TTransportFilterFactory.RegisterFilter(EncryptFilterName,

    TTransportEncryptFilter);

    finalization

    TTransportFilterFactory.UnregisterFilter(EncryptFilterName);

    使用客製化過濾器

    OK,回到範例伺服器,開啟ServerContainer程式單元並且在它的OnCreate事件處理函式中使用TDSTCPServerTransport的AddFilter方法加入我們的客製化過濾器:

    procedure TServerContainer1.DataModuleCreate(Sender: TObject);

    var

    i : Integer;

    begin

    i := DSTCPServerTransport1.Filters.AddFilter(uEncryptFilter.EncryptFilterName);

    for i := 0 to DSTCPServerTransport1.Filters.Count - 1 do

    Form1.lbFilters.Items.Add(DSTCPServerTransport1.Filters.GetFilter(i).Id);

    end;

    當然我們也需要在ServerContainer程式單元的uses句子中加入包含客製化過濾器的程式單元uEncryptFilter:

    implementation

    uses Windows, ServerMethodsUnit1, MainForm, uEncryptFilter;

    現在執行DataSnap伺服器,我們就可以看到伺服器顯示它已經找到了我們的客製化過濾器:

    clip_image008

    接著開啟用戶端應用程式,在主表單中也加入客製化過濾器的程式單元uEncryptFilter,編譯並且執行用戶端應用程式,再使用TCP Viewer觀察傳遞的資料,我們果然看到資料現在都經過加密了:

    clip_image010

    但是我們可以看到傳遞的資料量增加了。

    但是用戶端仍然可以正確的接受到資料:

    clip_image012

    當然我們也可以同時使用兩個過濾器,享受加密又壓縮的好處,下圖是伺服器同時支援了兩個過濾器:

    clip_image014

    如果我們再次使用TCP Viewer,就可以看到下圖,享受加密又壓縮的好處,因為資料加密了而且資料傳遞量又減少了。

    clip_image016

    現在您應該瞭解了如何使用DataSnap 2010的過濾器功能以及如何開發客製化過濾器,我們下次再討論DataSnap 2010的非同步機制。

    Have Fun!

    Comments (2)

    Please wait...
    Sorry, the comment you entered is too long. Please shorten it.
    You didn't enter anything. Please try again.
    Sorry, we can't add your comment right now. Please try again later.
    To add a comment, you need permission from your parent. Ask for permission
    Your parent has turned off comments.
    Sorry, we can't delete your comment right now. Please try again later.
    You've exceeded the maximum number of comments that can be left in one day. Please try again in 24 hours.
    Your account has had the ability to leave comments disabled because our systems indicate that you may be spamming other users. If you believe that your account has been disabled in error please contact Windows Live support.
    Complete the security check below to finish leaving your comment.
    The characters you type in the security check must match the characters in the picture or audio.

    To add a comment, sign in with your Windows Live ID (if you use Hotmail, Messenger, or Xbox LIVE, you have a Windows Live ID). Sign in


    Don't have a Windows Live ID? Sign up

    Bo Chen Linwrote:
    听说DataSnap2010新增一个In-Process的连接方式,大师能不能简单讲讲如何使用?
    Oct. 1
    Ronica Wangwrote:
    Thanks great for the blog, I am really drawed by datasnap+JSON in delphi 2010. It is so cool and looks like easy!
    Sept. 29

    Trackbacks

    The trackback URL for this entry is:
    http://gordonliwei.spaces.live.com/blog/cns!CCE1F10BD8108687!3853.trak
    Weblogs that reference this entry
    • None