Úvod
Minule jsem slíbil článek na téma REST server v Delphi pomocí kbmMW a tady je výsledek. Celý článek je postaven na reálných zkušenostech, které jsme doposud získali.
Než abych složitě vymýšlel vzorový příklad, dovolím si v textu použít odkazy na náš dokončený a reálně fungující server. Výsledná aplikace je dostupná na adrese edubazar.dosli.cz. Na této adrese provozujeme výměnný systém vytvořených digitálních učebních materiálů pomocí našich programů. Tento výměnný systém je plně integrován do aplikací EduBase a DoTest a veškeré stahování a nahrávání dat pak probíhá pomocí uvedených aplikací. Na adrese edubazar.dosli.cz je pouze front-end, který zobrazuje data tak, aby se kdokoliv mohl podívat, co je v EduBazaru nahráno.
Požadavky na server EduBazaru
Při tvorbě serverové části EduBazaru jsme potřebovali vytvořit systém, kde by jakákoliv aplikace (i třetích stran) mohla s EduBazarem komunikovat. Aby uložená data byla k dispozici všem bez nutnosti řešení komunikačních protokolů a podobně. Požadavkem bylo vytvoření vlastního API pro práci s EduBazarem a standardní komunikační protokol. Tím padla volba na vytvoření REST serveru. REST server pracuje tak, že klient (www prohlížeč, aplikace apod.) pošle dotaz na server a dostane odpověď s potřebnými daty. Součástí každého dotazu musí být vše, co je třeba k jeho zpracování. Nic víc, nic míň. Pro komunikaci s REST serverem postačí klientovi pouze možnost komunikace přes http protokol a parser formátu JSON.
Vyzkoušejte si…
Funkcionalitu EduBazar serveru si můžete snadno vyzkoušet pomocí následujících volání:
http://edubazar.dosli.cz/ebapi/folders
http://edubazar.dosli.cz/ebapi/folders?parentid=-1
http://edubazar.dosli.cz/ebapi/folders?parentid=7
Jedná se o API serveru používaná pro vybudování stromu složek v levém panelu. První příklad vrací celý strom najednou (pomalé pro delší strukturu). Druhé volání vrací pouze složky na nejvyšší úrovni (root level). Poslední příklad vrací všechny podsložky ze složky s ID 7. Server ve všech případech vrací korektně formátovaný výstup v JSON formátu.
Server
Velkou výhodou kbmMW je modulární struktura celého systému. Tzn., že jsme mohli použít klasický aplikační server (funkcionalitu), kterou jsme již měli implementovánu, a pouze doplnit náš stávající aplikační server o nový modul. Tím mám na mysli zejména práci s databází, SQL dotazy apod. Nový modul se pak na straně kbmMWServeru pouze zaregistruje a přiřadí se mu komunikační protokol (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;
Metoda StartUpEduBazar je volána ve chvíli, kdy se má server spustit.
Pro zajištění samotné funkcionality serveru je třeba vytvořit potomka TkbmMWEventHTTPService (v podstatě se jedná o běžný TDataModul s přidanou funkcionalitou). Pro vytvoření je nejvhodnější využít průvodce, který je součástí kbmMW. Dále už je práce velice jednoduchá. V modulu je nutné implementovat obsluhu událostí OnGet, OnPost, OnPut, OnHead, … dle potřeby.
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;
Jednotlivé dotazy na server pak zpracovávají metody 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;
No a to je na straně serveru v podstatě vše. Samozřejmě se jedná pouze o základ, uvedené řešení lze snadno rozšířit o další funkcionalitu. U většiny systémů, které jsme prozatím realizovali, se na straně serveru musí vyřešit správa sessions, ukládání stavu klienta, nastavení oprávnění apod. To vše ale již závisí na konkrétních požadavcích na systém.
Zpracování dat na straně klienta
Na straně klienta se zavolá funkce na REST serveru a následně se zpracuje vrácená odpověď. Protože se bavíme o webu, je následující ukázka v JavaScriptu, použít lze ale jakýkoliv programovací jazyk a nástroj, který podporuje JSON a http protokol. V ukázce je ukázka rekurzivní funkce, která načte hlavní úroveň stromu, a po kliknutí pomocí dalšího volání načte vybranou větev. Pro parsnutí JSON formátu je použito JQUERY.
<!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>
Poznámka: html stránku v praxi naleznete na edubazar.dosli.cz/test_tree.html.