kbmMW(n-tier framework) – 使用Delphi构建Rest服务器
介绍
上次我答应了一篇关于使用 kbmMW 在 Delphi 中 REST 服务器主题的文章,这是结果。整篇文章都是基于我们目前获得的真实经验。
我不会提出一个示例示例,而是冒昧地在文本中使用指向我们已完成且实际运行的服务器的链接。生成的应用程序可在 edubazar.dosli.cz 上获得。在这个地址,我们运营着一个使用我们的程序创建的数字学习材料的交换系统。该交换系统完全集成到 EduBase 和 DoTest 应用程序中,然后使用这些应用程序完成所有数据下载和上传。edubazar.dosli.cz 只有一个前端显示数据,以便任何人都可以看到 EduBazar 中上传的内容。
EduBazar 服务器要求
在创建 EduBazar 的服务器部分时,我们需要创建一个系统,任何应用程序(甚至是第三方应用程序)都可以与 EduBazar 进行通信。这样存储的数据就可以供所有人使用,而无需处理通信协议等。要求是创建一个自定义 API 来使用 EduBazar 和一个标准通信协议。这导致了创建REST服务器的选择。REST 服务器的工作方式是客户端(Web 浏览器、应用程序等)向服务器发送查询并接收包含必要数据的响应。每个查询都必须包含处理它所需的所有内容。仅此而已。要与 REST 服务器通信,客户端只需要能够通过 http 协议和 JSON 解析器进行通信。
试试看...
您可以使用以下调用轻松测试 EduBazar 服务器的功能:
http://edubazar.dosli.cz/ebapi/folders
http://edubazar.dosli.cz/ebapi/folders?parentid=-1
http://edubazar.dosli.cz/ebapi/folders?parentid=7
这是用于在左侧面板中构建文件夹树的服务器 API。第一个示例一次返回整棵树(对于较长的结构,速度较慢)。第二次调用仅返回顶层(根级别)的文件夹。最后一个示例返回文件夹 ID 7 中的所有子文件夹。在所有情况下,服务器都会以 JSON 格式返回格式正确的输出。
服务器
kbmMW的一大优势是整个系统的模块化结构。这意味着我们可以使用我们已经实现的经典应用服务器(功能),只需用一个新模块来补充我们现有的应用服务器。我的意思是特别使用数据库、SQL 查询等。然后,在 kbmMWServer 端注册新模块,并为其分配通信协议 (TkbmMWTCPIPIndyServerTransport)。
unit MyServer;
type
TMyServer = class(TDataModule)
// základní komponenta pro vytvoření serveru kbmMW
// v rámci jednoho serveru lze registrovat desítky různých modul,
// které obsahují samotnou funkcionalitu serveru
kbmMWServer: TkbmMWServer;
kbmMWTCPIPIndyServerTransport: TkbmMWTCPIPIndyServerTransport;
procedure StartUpEduBazarServer;
end;
implementation
procedure TMyServer.StartUpEduBazarServer;
var
lServiceDef: TkbmMWCustomServiceDefinition;
begin
// registrace modulu TdmEduBazarHTTPService v kontextu aplikačního serveru kbmMW
lServiceDef:= kbmMWServer.RegisterService(TdmEduBazarHTTPService, True);
// nastavení složek pro www server
TkbmMWHTTPServiceDefinition(lServiceDef).RootPath[mwhfcHTML] := 'wwwroot';
TkbmMWHTTPServiceDefinition(lServiceDef).RootPath[mwhfcImage] := 'wwwroot\images';
TkbmMWHTTPServiceDefinition(lServiceDef).RootPath[mwhfcJavascript]:= 'wwwroot\scripts';
TkbmMWHTTPServiceDefinition(lServiceDef).RootPath[mwhfcStyleSheet]:='wwwroot\styles';
TkbmMWHTTPServiceDefinition(lServiceDef).RootPath[mwhfcOther] :='wwwroot\files';
// přiřazení komunikačního protokolu serveru
// jeden server může „poslouchat“ na různých portech. A na každém portu
// může být jiný komunikační protokol.
kbmMWTCPIPIndyServerTransport.Server := kbmMWServer;
kbmMWTCPIPIndyServerTransport.StreamFormat := AJAX;
kbmMWTCPIPIndyServerTransport.Bindings := 0.0.0.0:80;
kbmMWServer.Active := True;
end;
当服务器即将启动时,将调用 StartUpEduBazar 方法。
为了确保服务器本身的功能,有必要创建一个后代 TkbmMWEventHTTPService(基本上它是一个具有附加功能的常规 TDataModule)。若要创建它,最好使用属于 kbmMW 的向导。在那之后,工作非常简单。在模块中,需要实现事件处理程序 OnGet、OnPost、OnPut、OnHead、...根据需要。
unit dmEduBazarHTTPServiceUnit;
type
TdmEduBazarHTTPService = class(TkbmMWEventHTTPService)
// základní datamodul, který obsahuje Eventy a vlastnosti
//potřebné pro vytvoření http serveru
// popis metod je u jejich implementace níže
class function GetPrefServiceName:string;
function TdmEduBazarHTTPService.kbmMWEventHTTPServiceGet(const Func: string;
const ClientIdent: TkbmMWClientIdentity; Args: TkbmMWVariantList): Variant;
end;
class function TdmEduBazarHTTPService.GetPrefServiceName:string;
begin
Result:='HTTPSERVICE';
// tento název je klíčový pro celé fungování. V rámci serveru je možné mít
// pouze jednu service, s tímto názvem v případě uvedení jiného názvu,
// je nutné při dotazu na server uvést také název požadované služby,
// ale tím by se vše komplikovalo.
end;
function TdmEduBazarHTTPService.kbmMWEventHTTPServiceGet(const Func: string;
const ClientIdent: TkbmMWClientIdentity; Args: TkbmMWVariantList): Variant;
var
lCharset: string;
lMimeType: string;
lURL: string;
begin
// získání url dotazu
lURL := VarToStr(Args[0]);
if lURL = '/ebapi/folders' then
result := Perform_EBAPI_Folders(ClientIdent, Args)
else
if lURL = '/ebapi/packages' then
result := … // provedení činností pro vrácení správných dat atd.
else
begin
// klient požaduje URL, které není definováno jako API.
//Může jít například o html soubor, JS soubor, obrázek atd. Tzn., že
// potřebujeme vrátit odpověď jako by se jednalo o klasický http server
// následující metody jsou přímo součástí kbmMW.
result := HTTPResponseFromFile(lURL, lMimeType, lCharset);
SetResponseMimeType(lMimeType);
SetResponseCharset(lCharSet);
end;
end;
end;
然后,通过Perform_EBAPI_方法处理对服务器的单个查询。
function TdmEduBazarHTTPService.Perform_EBAPI_Folders(
const ClientIdent: TkbmMWClientIdentity; Args: TkbmMWVariantList): variant;
var
lParentID: Integer;
lJSONArray: TkbmMWJSONArray;
lJSONObject : TkbmMWJSONObject;
begin
// kbmMW obsahuje nástroje pro parsnutí parametrů z url http requestu.
lParentID := GetQueryValue(Args[2], 'parentid', -2);
// po získání parametrů je vytvořen SQL dotaz na získání potřebných dat z databáze.
// bžná práce s DB.
qryTemp…. // vypuštěno pro zkrácení
qryTemp.Active := True;
// Vytvoření JSON objektu z qryTemp
// kbmMW obsahuje vše potřebné pro práci s JSON formátem
// tady máte na výběr z několika různých možností jak převést DataSet na JSON.
// pokud se použije virtualizace přístupu k databázi přes kbmMWXClientDataSet
//(což je potomek TkbmMemTable), je možné použít volání
//kbmMemTable.SaveToStreamViaFormat,
//kde JSON je jeden z podporovaných formátů, nebo použít následující kód.
lJSONArray := TkbmMWJSONArray.Create
try
// následuje zkrácený kód
// pro každý řádek v tabulce provést
lJSONObject := TkbmMWJSONObject.Create;
// pro každý sloupec v qryTemp provést
lJSONObject.AsString[COLUMN_NAME] := VALUE;
lJSONArray.Add(lJSONObject);
// konec zkrácené verze
// po vytvoření pole JSON objektů poslat odpověď klientovi
lJSONStreamer:=TkbmMWJSONStreamer.Create;
try
SetResponseMimeType('application/json');
SetResponseCharset('utf-8');
result := lJSONStreamer.SaveToUTF8String(lJSONArray);
finally
lJSONStreamer.Free;
end;
finally
lJSONArray.Free;
end;
end;
嗯,在服务器端差不多就是这样。当然,这只是基础,该解决方案可以通过附加功能轻松扩展。对于我们目前实现的大多数系统,会话管理、客户端状态存储、权限设置等都必须在服务器端解决。但这完全取决于系统的具体要求。
客户端数据处理
在客户端,调用 REST 服务器上的函数,然后处理返回的响应。由于我们谈论的是 Web,因此以下示例是 JavaScript 语言,但您可以使用任何支持 JSON 和 http 协议的编程语言和工具。此示例演示一个递归函数,该函数检索树的主级别,单击该函数时,使用下一个调用来检索所选分支。JQUERY用于解析JSON格式。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<script type="text/javascript" language="javascript" src="jquery.min.js">
</script>
<title>Build tree</title>
<style>
span{
color: rgb(0,102,204);
cursor:pointer;
}
</style>
</head>
<body>
<script>
function buildTree(target, parent){ // volání funkce na REST serveru
$.get('http://edubazar.dosli.cz/ebapi/folders', {parentid: parseInt(parent)},
function(data) {
var resHtml = "<ul>";
for (var i=1; i<data.length; i++){ // Průchod všemi objekty
var title = data[i]["Name"];
var id = data[i]["ID"];
resHtml += '<li data-id="'+id+'">';
if (data[i]["SubtreeIDs"] != ""){
resHtml += '<span onclick="buildTree($(this).parent(), '+id+')">'
+title+'</span>';
}else{
resHtml += title;
}
resHtml += '</li>';
}
resHtml += '</ul>';
$(target).append(resHtml);
});
}
buildTree("body", -1);
</script>
</body>
</html>
注意:实际上的html页面可以在 edubazar.dosli.cz/test_tree.html 上找到。