[Delphi] YxdIOCP 之 MVC 簡介


    最近為 YxdIocp 開源庫增加了這個輕量級的 MVC 支持。其實說是 MVC ,但由於現在還沒有配套的視圖模版引擎,所以在V上面很虛弱。不過本着做數據服務為主的思想,現在已經基本夠用了。下面來簡單介紹一下它吧。

    要使用 MVC 功能非常簡單, 引用 iocp.Http.MVC 單元,或者在窗體上放入一個 TIocpHttpMvcServer 組件即可。然后就是編寫業務代碼 (可參考開源庫中 demo\IcopHttpSvrMVC 中的代碼)。iocp.Http.MVC 引入了多個屬性標注,它是 MVC 的靈魂,要用好 MVC, 必須了解這些屬性標注的功能和意義。MVC引擎會使用掃描器根據這些標注自動掃描注入,響應客戶請求。

    在此之前,我們需要注意一點,在 Delphi 中, 屬性標注定義的名稱尾部如果是 "Attribute",  在使用時可以直接省略后面的 "Attribute" ,比如定義了“ServiceAttribute”標注,使用時可以只輸入 "[Service]" 即可。下面講到的屬性標注,都是省略掉了 "Attribute" 。

 

一、屬性標注說明

[Service]

業務層標識性標注,為指定類添加此標注后,此類才會被MVC引擎識別,從而具備業務請求處理能力。

uses
  iocp.Http.MVC, iocp.Http, SysUtils, Classes;

type
  [Service]
  THelloMvc = class(TObject)
  public
  end;

在上面的示例代碼中,由於加入了 [Service] 標識,會被 MVC 掃描器發現該類,並自動創建一個單例用於響應業務請求。

 

[Controller]

控制層標注。增加此標注后,指定類才具備處理業務請求的能力。此標注和 [Service] 功能一樣,只是方便在一些情況下區分業務類別。

uses
  iocp.Http.MVC, iocp.Http, SysUtils, Classes;

type
  [Controller]
  THelloMvc = class(TObject)
  public
  end;

 

[Autowired]

自動裝配標注。為一個類中的變量增加此標注后,MVC 引擎會在實例化此類時,為這個成員變量自動注入適當的值。(目前由於暫無IOC實現,只能自動注入TIocpWebSocketServer、Ser ver)

type
  [Controller]
  THelloMvc = class(TObject)
  protected
    [Autowired]
    FServer: TIocpHttpServer;
  public
  end;

程序運行后,會自動將 FServer 初始化為 HttpMVC.Server 。

[RequestMapping]

請求地址映射標注。此標注可用於類級別,也可用於類中的函數或方法上。

此標注有如下幾個屬性:

  • Value:  指定請求的實際地址。
  • Method: 指定請求的method類型, 它是 TIocpHttpMethod 類型的枚舉值,可以是 GET、POST、PUT、DELETE 等。
  • Consumes: 指定處理請求的提交內容類型(Content-Type),例如application/json, text/html。
  • Produces: 指定返回的內容類型,僅當request請求頭中的(Accept)類型中包含該指定類型才返回。
  • Params: 指定request中必須包含某些參數值時,才讓該方法處理。
  • Headers: 指定request中必須包含某些指定的header值,才能讓該方法處理請求。

注意: 在類級別使用此標注時, Params 屬性無效。類級別主要用於設置URL的基址,以及一些默認值。比如在類級別指定 Method 為 Post,那么類中的函數或方法映射時,如果未指定 Method, 那么默認就是 Post。另外,在函數或方法被觸發時,以下類型的參數會被自動注入:

  • TIocpHttpRequest: 自動注入為當前Request對象。
  • TIocpHttpResponse: 自動注入會當前Response對象。
  • TIocpHttpServer: 自動注入會當前Server對象。
  • TIocpWebSocketServer: 啟用 WebSocket 時,自動注入當前 Server 對象。
  • TIocpHttpWriter: 自動注入 Response.GetOutWriter 對象,用於直接輸出返回數據。
  • TIocpHttpConnectionTIocpClientContext:自動注入當前連接對象(Request.Connection)。
  • TMemoryStream 或基於 TStream: 自動注入當前請求的數據流對象(Request.Data)。

  另外,如果參數名是 “RequestData”, 且為字符串類型時,自動注入 Request.DataString 。

 

示例

unit Unit2;

interface

uses
  iocp.Http.MVC, iocp.Http, SysUtils, Classes;

type
  [Controller]
  [RequestMapping('/hello')]
  THelloMvc = class(TObject)
  public
    // 映射地址:/hello/view1
    // 響應所有類型的請求(Get, Post, Put 等等)
    [RequestMapping('/view1')]
    function View1(Request: TIocpHttpRequest): string;

    // 映射地址:/hello/view2
    // 響應 Get 請求
    [RequestMapping('/view2', http_GET)]
    function View2(Request: TIocpHttpRequest): string;

    // 映射地址:/hello/view3/aaa?uid=123
    // 響應 Get 請求, 並且必須是包含參數uid,值為123時才響應
    // 如果參數中不包括uid,或者uid的值不為123,會返回405錯誤
    [RequestMapping('/view3/aaa', http_GET, 'uid=123')]
    function View3(Request: TIocpHttpRequest): string;

    // 映射地址:/hello/view4?uid=123
    // 必須是 Get 請求, Content-Type必須包含application/json,
    // Accept必須是"*.*"或者包含“application/json” ,
    // 必須包含參數uid=123
    // 如果不符合條件會返回405錯誤
    [RequestMapping('/view4', http_GET, 'application/json', 'application/json', 'uid=123')]
    function View4(Request: TIocpHttpRequest): string;
  end;

implementation

{ THelloMvc }

function THelloMvc.View1(Request: TIocpHttpRequest): string;
begin
  Result := 'httpPostTest.html';
end;

function THelloMvc.View2(Request: TIocpHttpRequest): string;
begin
  Result := 'httpPostTest.html';
end;

function THelloMvc.View3(Request: TIocpHttpRequest): string;
begin
  Result := 'httpPostTest.html';
end;

function THelloMvc.View4(Request: TIocpHttpRequest): string;
begin
  Result := 'httpPostTest.html';
end;

initialization
  THelloMvc.RegToMVC;

end.

如果服務器Host是: http://127.0.0.1:8080,

那么,

 

URL: http://127.0.0.1:8080/view1

HTTP: 不限(GET, POST, PUT...)

觸發: THelloMvc.View1

 

URL: http://127.0.0.1:8080/view2

HTTP: GET

觸發: THelloMvc.View2

 

URL: http://127.0.0.1:8080/view3/aaa?uid=123

HTTP: GET

觸發: THelloMvc.View3

 

URL: http://127.0.0.1:8080/view3/aaa?uid=789

HTTP: GET

觸發: 無,返回405錯誤

 

URL: http://127.0.0.1:8080/view3

HTTP: GET

觸發: 無,返回404錯誤

 

URL: http://127.0.0.1:8080/view4?uid=123

HTTP: GET

觸發: 無,返回405錯誤

原因: 沒有在Http Header 的"Content-Type"屬性中添加 "application/JSON",需要使用類似下面的請求頭才可以正常訪問:

GET /hello/view4?uid=123 HTTP/1.1
Accept: */*
User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:12.0) Gecko/20100101 Firefox/12.0
Content-Type: application/JSON; charset=GB2312
Accept-Encoding: gzip, deflate
Content-Length: 0
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
Connection: Keep-Alive

使用上面的請求頭,會觸發 THelloMvc.View4 。

[Download]

標識請求返回頁面采用下載方式。加入此標注后,會在Http響應頭中加入文件下載標識,瀏覽會以文件下載的方式讀取數據,一般會提示用戶保存文件。

type
  [Controller]
  THelloMvc = class(TObject)
  public
    [Download]
    function FileDownload(Request: TIocpHttpRequest): string;
  end;

 

[WebSocket]

WebSocket 請求處理標注。當類中的一個函數或方法要用來處理 WebSocket 請求時,需要添加此標注。此標注不能與 [RequestMapping] 混用。

此標注有如下幾個屬性:

  • Data: 指定僅當Data為指定內容的文本消息時才響應。
    // WebSocket 請求處理,直接返回字符串內容
    [WebSocket]
    function HelloWebSocket(): string;

    // WebSocket 請求處理, 只有接收到文本信息且內容是 'hello' 時才響應
    [WebSocket('hello')]
    procedure HelloWebSocket2(Response: TIocpWebSocketResponse);

 

[PathVariable]

這是一個參數級的屬性標注,用來獲得請求url中的動態參數,並綁定到處理函數中指定參數上。如果參數類型與實際傳入的內容不符,會產生 500 錯誤。

此標注有如下幾個屬性:

  • Name: 當方法參數名稱和需要綁定的uri template中變量名稱不一致時, 用於指定uri template的名稱。如果一致,可以省略。

URL中的動態參數,需要以 "{}" 包圍起來。

    [RequestMapping('/view/{uid}/{uname}', http_GET)]
    procedure ViewTest1(
      [PathVariable('uid')] UID: Integer;
      [PathVariable('uname')] const UName: string;
      Response: TIocpHttpWriter);

示例中,{uid}、{uname} 都是url級的動態參數。通過 PathVariable 標注將它們分別綁定到了處理函數 ViewTest1 的參數 UID 和 UName 上。在觸發 ViewTest1 時,UID 的值就是 {uid} , UName 就是 {uname} 。

 

[RequestParam]

這是一個參數級的屬性標注。用於將請求參數區數據映射到功能處理方法的參數上。如果實際URL中參數不存在時,會將參數置為空(如數字型會是0,字符串會是空串)。如果參數類型與實際傳入的內容不符,會產生 500 錯誤。

此標注有如下幾個屬性:

  • Name: 參數名稱。
    // 返回一個網頁名稱, 以下載方式
    // 處理 URL: /demo/view4?uid=123456
    [RequestMapping('/view4', http_GET)]
    function ViewTest4([RequestParam('uid')] UID: Integer): string;

示例中,當 url 是 http://host/demo/view4?uid=123456 時, 觸發 ViewTest4 時,參數 UID 的值會是 123456。

 

[RequestBody]

這是一個函數(方法)級的屬性標注。它的作用如下:

1. 讀取Request請求的body部分數據,使用系統默認配置的Converter(轉換器)進行解析,然后把相應的數據綁定到要返回的對象上;

2. 再把Converter返回的對象數據綁定到controller中方法的參數上。

示例:

type
  TUserData = record
    UID: Integer;
    Age: Integer;
    Name: string;
    Nick: string;
  end;
 
    ......

   // 提交用戶數據
    // 處理 URL: /demo/person/profile/reguser
    [RequestMapping('/person/profile/reguser')]
    function RegUser([RequestBody] Data: TUserData): string;

在觸發 RegUser 函數時, 轉換器會將請求的數據注入 Data 中。在編寫轉換器時,GET請求一般需要單獨處理。

轉換器示例:

設置 HttpMvc.OnDeSerializeData 事件,在事件中進行處理:

// 反序列化處理
function TForm1.DoDeSerializeData(Sender: TObject; const Value: string;
  const Dest: TValue; IsGet: Boolean): Boolean;
var
  Json: JSONObject;
  S: AnsiString;
begin
  if not IsGet then begin
    Json := JSONObject.ParseObject(Value, False);
    try
      if Assigned(Json) then
        TYxdSerialize.ReadValue(Json, Dest);
    finally
      FreeAndNil(Json);
    end;
  end else begin
    // Get 請求單獨處理
    Json := JSONObject.Create;
    try
      S := AnsiString(Value);
      HttpMvc.Server.DecodeParam(PAnsiChar(S), Length(S), DoReadedItem, Json);
      TYxdSerialize.ReadValue(Json, Dest);
    finally
      FreeAndNil(Json);
    end;
  end;
  Result := True;
end;

在上面的反序列化操作中, Dest 實際上就是標注了 RequestBody 的響應函數中的正要注入的參數,在示例中就是 Data: TUserData。

如果有這樣的一個Post請求:

POST /demo/person/profile/reguser HTTP/1.1
Accept: */*
User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:12.0) Gecko/20100101 Firefox/12.0
Content-Type: text/html; charset=GB2312
Accept-Encoding: gzip, deflate
Content-Length: 59
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
Connection: Keep-Alive

{"UID":666,"Age":30,"Name":"yangyxd","Nick":"\u55B5\u55B5"}

在觸發 RegUser 時, 參數 Data 的值會是: 

Name    Value
Data    (666, 30, 'yangyxd', '喵喵')


 

[ResponseBody]

這是一個函數(方法)級的屬性標注。它的作用如下:

將Controller的方法返回的對象,通過適當的Converter的Adapter轉換對象, 將內容轉換為指定格式后,寫入到Response對象的body數據區。

    // 根據UID查詢用戶信息。
    // 處理 URL: /demo/person/profile/123456
    [RequestMapping('/person/profile/{id}', http_GET)]
    [ResponseBody]
    function Porfile([PathVariable('id')] UID: Integer): TPerson;

在觸發 Porfile 函數時, 返回值 TPerson 會被轉換器轉換序列化為指定格式,然后寫入 Response 對象, 返回給客戶端。

設置 HttpMvc.OnSerializeData 事件,在事件中進行處理:

// 序列化處理
function TForm1.DoSerializeData(Sender: TObject; const Value: TValue): string;
var
  Json: JSONObject;
begin
  Json := JSONObject.Create;
  try
    TYxdSerialize.WriteValue(Json, '', Value);
  finally
    Result := Json.ToString();
    Json.Free;
  end;
end;

如果在 Porfile 中的返回內容是:

function TMvcDemo.Porfile(UID: Integer): TPerson;
begin
  Result.UID := UID;
  Result.Name := 'Admin';
  Result.Status := 100;
end;

通過使用上面的轉換器, 在觸發 Porfile 函數后,瀏覽器收到的數據會是:

{"UID":123456,"Name":"Admin","Status":100}

 

二、映射函數返回值

普通WEB請求:

無返回值時, 會返回客戶端 200 狀態。

返回字符串時, 會返回一個  Prefix + 返回值 + Suffix 的文件。如果文件不存在,則返回 404 錯誤。

返回整數時, 認為是錯誤代碼。比如返回 404, 則瀏覽器會收到 404 錯誤。

返回對象(Class) 或記錄(Record)時,如果標注了ResponseBody,會使用轉換器轉換后輸出給客戶端。如果沒有標注 ResponseBody, 則直接返回 200 狀態。如果返回的是對象,會自動釋放對象。

 

WebSocket 請求:

無返回值時, 服務器不作任何響應。

返回字符串時,會直接將字符串發送給客戶端。

返回數據時,會將數據轉為字符串后發送給客戶端。

返回對象(Class) 或記錄(Record)時,如果標注了ResponseBody,會使用轉換器轉換后輸出給客戶端。如果沒有標注 ResponseBody, 服務器不作任何響應。

 

三、 MVC 服務器配置

在窗口上放置 TIocpHttpMvcServer 組件時, 通過組件屬性面板進行設置。

不使用 TIocpHttpMvcServer 組件時, 引用 iocp.Http.Mvc , 會自動開啟 MVC 服務。此時會自動加載配置文件 "http_mvc_setting.xml"。

配置文件應當放於服務程序相同目錄中,文件名為 "http_mvc_setting.xml"

配置文件示例:

<?xml version="1.0" encoding="UTF-8"?>
<xml>
    <ListenPort>8081</ListenPort>
    <Active>true</Active>
    <Charset>UTF-8</Charset>
    <UseWebSocket>false</UseWebSocket>
    <ContentLanguage/>
    <WebPath>.\Web\</WebPath>
    <GzipFileTypes>.htm;.html;.css;.js;.txt;.xml;.csv;.ics;.sgml;.c;.h;.pas;.cpp;.java;</GzipFileTypes>
    <AutoDecodePostParams>true</AutoDecodePostParams>
    <UploadMaxDataSize>2097152</UploadMaxDataSize>
    <AccessControlAllow>
        <Enabled>false</Enabled>
        <Origin>*</Origin>
        <Methods>POST, GET, OPTIONS</Methods>
        <Headers>X-Requested-With, Content-Type</Headers>
    </AccessControlAllow>
</xml>

配置文件說明:

ListenPort: 服務器監聽端口

Active: 是否在程序運行后自動開始服務

Charset: 默認字符集

UseWebSocket: 是否使用WebSocket服務

ContentLanguage: 默認內容語言

WebPath: WEB服務文件根目錄

Prefix: 返回視圖的前綴名稱,默認為空

Suffix: 返回視圖的后綴名稱,默認為空

UriCaseSensitive: URI是否大小寫敏感

BindAddr: 服務器端口綁定地址,默認為“0.0.0.0”

GzipFileTypes: 自動使用gzip壓縮數據的文件類型

AutoDecodePostParams: 自動解析Post方式傳入的參數

UploadMaxDataSize: 上傳文件的最大大小(字節)。

AccessControlAllow: WEB請求跨域控制選項。

  Enabled: 是否啟用跨域控制

  Origin: 允許哪些url可以跨域請求到本域

  Methods: 允許的請求方法

  Headers: 允許哪些請求頭可以跨域

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM