kbmMW (n-tier framework) – Rest server

vložil Petr Slípek 11. prosince 2012 01:51

Ú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.

Tagy: , ,

Komentáře

11.12.2012 8:29:17 #

Petr Slípek

Ač jsem to po sobě několikrát četl, tak jsem se nevyhnul chybě. V posledním Delphi kódu má být na řádku 23 správně lJSONArray := TkbmMWJSONArray.Create;

Petr Slípek

11.12.2012 9:48:39 #

radekc

opraveno, díky

radekc

Komentování ukončeno

Naše nabídka

MVP
Ing. Radek Červinka - Embarcadero MVP
profil na linkedin, Twitter:@delphicz

Nabízím placené poradenství a konzultace v oblasti programování a vývoje SW.
Dále nabízíme i vývoj speciálního software na zakázku.

Neváhejte nás kontaktovat (i ohledně reklamy).

love Delphi

O Delphi.cz

Delphi je moderní RAD nástroj podporující tvorbu nativních aplikací pro platformu Win32, Win64, Mac OSX, Linux a na iPhone a Android.

Delphi.cz je nezávislý portál pro uživatele Delphi. Portál není koncipován pro úplné začátečníky, i když i ti se zde nebudou nudit, ale spíše na programátory, kteří již něco znají a chtějí své znalosti dále rozvíjet a sledovat novinky.

Poslední komentáře

Comment RSS

Dle měsíců