|
Antwort |
Registriert seit: 8. Jan 2007 472 Beiträge |
#1
Der dritte Artikel der mORMot Vorstellungsreihe behandelt das erste Mal den Bereich, für den die Bibliothek erstellt wurde. Arnaud Bouchez bezeichnet sein Werk als Client-Server ORM/SOA-Framework und schreibt dazu: "The Synopse mORMot framework implements a Client-Server RESTful architecture, trying to follow some MVC, N-Tier, ORM, SOA best-practice patterns". Die Umsetzung passiert auf den Grundsätzen dieser Architektur Prinzipien. Wem die Begriffe SOA und RESTful-Architektur nicht viel sagen, kann sich unter folgenden Links einlesen:
Im Anhang befindet sich der Sourcecode und das ausführbare Programm für Server und Client. Disclaimer: Der Sourcecode ist weder getestet noch optimiert. Er sollte mit Delphi ab Version 10.2 funktionieren. Die Benutzung der zur Verfügung gestellten Materialien erfolgt auf eigene Gefahr. Und heute noch verschärft: Wer den Beispiel-Server trotz aller Warnungen im Internet zugänglich macht, soll zur Strafe den folgenden Satz 1000-mal schreiben: "Ich werde nie wieder ein Programm in einem Bereich einsetzen, für das es nicht vorgesehen wurde!". Der Vollzug wird von mir persönlich überwacht. Angelehnt an die ersten beiden Beispiel-Anwendungen werden auch hier wieder Bilder verwaltet. Der Server speichert die Bilder im benutzereigenen Verzeichnis (jeder Benutzer hat sein eigenes Verzeichnis). Mit dem Beispiel Quelltext für den Server und Client bekommt man:
Die folgenden Punkte werden nur kurz angerissen. Jeder Einzelne würde eine eigene Abhandlung rechtfertigen. Sie sind zum Verstehen der Beispiel-Anwendung wichtig. 1) REST-Server Der RestServer erbt die REST-Funktionalität von der Klasse TRestServer* und ihrer Nachfahren. In einem Programm können beliebig viele RestServer mit jeweils eigenen Model betrieben werden. Jedoch muss jedes Model einen eindeutigen Rootnamen haben. In einer Shop-Anwendung ist es zum Beispiel sinnvoll, die Bereiche Administration und Shop mit ihren jeweiligen Funktionen auf spezialisierte RestServer zu verteilen. Wenn der RestServer "Shop" unter dem Rootnamen Shop registriert ist, wird eine Service Funktion ArticleBuy über den Root Shop/ArticleBuy aufgerufen. Eine völlständige URI könnte wie folgt aussehen:
Code:
In mORMot sind zur Zeit diese Nachfahren der TRestServer Klasse vorhanden:
https://www.domain.de/Shop/ArticleBuy?ArticleID=123&AccessToken=xyz
Der RestServer kann stand-alone, also direkt über seine Methoden oder in einem Client-Server-Modell über die Kommunikationsschicht HTTP/1.0/1.1 über TCP/IP angesprochen werden. Die Möglichkeiten hängen vom verwendeten Protokoll ab. Für eine Client-Server Anwendung findet allgemein Verwendung, spezielle Fälle wie WebSockets lassen wir mal außen vor, auf dem Server die Klasse TRestHttpServer und dem Client die Klasse TRestHttpClient* oder Nachfahren. Beim Instanziieren der Klasse TRestHttpServer stehen verschiede Server-Modi zur Auswahl:
3) Authentifizierung Die in mORMot vorhanden Authentifizierungsklassen sind: TRestServerAuthentication* None, Default, HttpBasic und Sspi. Als Alternative ist die Verwendung von JWT (JSON Web Tokens) möglich. Im Beispiel kommt die mORMot eigene proprietäre Lösung zur Anwendung. Sie ist in der Klasse TRestServerAuthenticationDefault implementiert. Im Beispiel-Server ist die Protokollierung aktiviert. Im Ausdruck lässt sich das Ping/Pong zwischen Client und Server zum Aufbau einer Session verfolgen. Das Verfahren ist ein guter Kompromiss zwischen Sicherheit und Geschwindigkeit. Auch wenn die Authentifizierung aktiviert ist, können einzelne Methoden durch Aufruf der RestServer Funktion ServiceMethodByPassAuthentication('MethodName') davon ausgenommen werden. Eine der standardmäßig frei zugänglichen Funktionen ist Timestamp. Zum Testen am Beispiel-Server folgenden Aufruf im Browser eingeben:
Code:
Nach Ausführung wird im Browser Fenster eine mehrstellige Nummer, der Timestamp des Servers, angezeigt.
http://localhost:8080/Files/Timestamp
Beschleunigung beim Speichern und Lesen von Bildern Der letzte Punkt in der Liste wurde schon in den vorherigen Artikel besprochen. Er wird nur zur Komplementierung noch einmal erwähnt. Hier der Link zu den Benchmark-Werten. Der eigene RestServer In mORMot unterscheidet man zwei Arten von Services:
Das Interface des Rest-Servers ist sehr einfach gehalten und umfasst nur wenige Funktionen:
Delphi-Quellcode:
Wie das Thema schon vorgibt, implementieren wir methodenbasierte Services. Sieht man sich die Definition an, fallen die drei Funktionen LoadFile, SaveFile und GetAllFileNames im published Abschnitt auf. Der Prototyp ist wie folgt definiert:
TFileRestServer = class(TRestServerFullMemory)
strict private FDataFolder: TFileName; protected const DEFAULT_FILE_EXT = '._'; protected function GetSessionUserDirName(pmCtxt: TRestServerUriContext; out pmoDirName: TFileName): Boolean; property DataFolder: TFileName read FDataFolder; public constructor Create(const pmcRootName: RawUtf8; const pmcDataFolder: TFileName); reintroduce; function InitAuthForAllCustomers(const pmcFileName: TFileName): Boolean; published procedure LoadFile(pmCtxt: TRestServerUriContext); procedure SaveFile(pmCtxt: TRestServerUriContext); procedure GetAllFileNames(pmCtxt: TRestServerUriContext); end;
Delphi-Quellcode:
Jede auf diese Weise definiert Methode im published Abschnitt wird automatisch als Service Funktion registriert. Der Name der Methode ist der Servicename. Geroutet wird immer über RootName/ServiceName. Im besprochenen Beispiel ist RootName "Files". Damit sieht die Route für die Funktion GetAllFileNames wie folgt aus:
type
TOnRestServerCallBack = procedure(pmCtxt: TRestServerUriContext) of Object;
Code:
Methoden lassen sich mit der RestServer Funktion ServiceMethodRegister('MethodName', MyRestServerCallBackEvent) auch direkt registrieren. Im published Abschnitt stehende Methoden dieses Typs werden automatisch registriert. Ist doch gar nicht kompliziert.
GET Files/GetAllFileNames
Einen Service implementieren Schauen wir uns ein konkretes Beispiel an. Die Abfrage des Services GetAllFileNames liefert die Inhaltsliste des benutzereigenen Datenverzeichnisses zurück. Das Ergebnis wird im JSON Format zurückgeliefert.
Delphi-Quellcode:
Ganz wichtig zu beachten ist der Grundsatz: Vertraue niemals Daten, die von außen kommen! Unsere Funktion benötigt keine Eingangsparameter. Die Autorisierung des Zugriffs auf die Funktion von außen wird schon über die Authentifizierung berechtigt. Die Klasse TRestServerUriContext besitzt eine Vielzahl von Funktionen, wie z.B. die komfortable Handhabung der Ein- und Ausgangswerte, oder der Session Daten. Zur Serialisierung der Daten wird die Funktion DynArraySaveJson verwendet. Mit dem mORMot Werkzeugkasten bleibt kein Wunsch offen und einfacher gehts auch nicht.
procedure TFileRestServer.GetAllFileNames(pmCtxt: TRestServerUriContext);
var dirName: TFileName; dirFiles: TFileNameDynArray; begin if (pmCtxt.Method = mGET) and GetSessionUserDirName(pmCtxt, dirName) then begin dirFiles := FileNames([DataFolder, dirName], FILES_ALL, [ffoSortByName, ffoExcludesDir]); for var i: Integer := Low(dirFiles) to High(dirFiles) do begin if ExtractFileExt(dirFiles[i]) = DEFAULT_FILE_EXT then dirFiles[i] := GetFileNameWithoutExt(dirFiles[i]); end; pmCtxt.Returns(DynArraySaveJson(dirFiles, TypeInfo(TFileNameDynArray)), HTTP_SUCCESS); end else pmCtxt.Error(StringToUtf8(SErrHttpForbidden), HTTP_FORBIDDEN); end; Benutzerverwaltung für den Server Beim Instanziieren des RestServers wird die Authentifizierung aktiviert. Die Verwaltung wird mit den ORM Klassen TAuthGroup und TAuthUser oder deren Nachfahren aufgebaut. Unbedingt beachten muss man, dass in beiden Tabellen Vorgaben automatisch erstellt werden, wenn dieses Verhalten nicht explizit unterdrückt wird. Die angelegten Datensätze für die Gruppe sind nützlich, aber alle Benutzer sollten selbst erstellt werden. Die Benutzerdaten im Beispiel werden aus der Datei "Customer.config" im Programmverzeichnis geladen. Die gesamte Umsetzung erfolgt in wenigen Zeilen Sourcecode. Der vollständige Server Ein Server sollte auf Basis der Klasse TSynDaemon erstellt werden. Zwecks der besseren Übersichtlichkeit habe ich für das Beispiel eine einfachere Variante gewählt. Das Programm besteht aus zwei Komponenten, dem oben beschriebenen RestServer und dem Übertragungsprotokoll HTTP. Mehr Aufwand ist nicht notwendig, einen HTTP-Server an den Start zu bringen:
Delphi-Quellcode:
Für das URL-Präfix wird ein Wildcard Zeichen verwendet, "+" bindet an alle Domainnamen für den angegebenen Port.
function TTestServerMain.RunServer(const pmcPort: RawUtf8): Boolean;
begin Result := False; if (FHttpServer = Nil) and FRestServer.InitAuthForAllCustomers(FCustomerConfigFile) then begin FHttpServer := TRestHttpServer.Create(pmcPort, [FRestServer], '+' {DomainName}, useHttpSocket {or useHttpAsync}); FHttpServer.AccessControlAllowOrigin := '*'; Result := True; end; end; Die Client Anwendung Am einfachsten bauen wir uns ein Pendant zur Klasse TFileRestServer für den Client. Darin wird die komplette REST Logik gekapselt. Als Vorfahre kommt die Klasse TRestHttpClientWinHttp zur Anwendung. Sie eröffnet den Zugriff auf die Klasse TWinHttpApi, die eine einfache Anbindung unserer Fortschrittsanzeige ermöglicht. Jede Service Funktion des Servers bekommt ihr Gegenstück, mit der für den Client notwendigen Funktionalität:
Delphi-Quellcode:
Über die CallBack* Funktionen werden die Service Methoden auf dem Server aufgerufen. Dabei wird das RESTful Root-Schema ModelRoot/MethodName verwendet. Benötigt ein Aufruf Parameter, wird der Query-String mit Hilfe der Funktion UrlEncode durch Übergabe von Name/Value Pairs gebildet. Im Erfolgsfall wird 200/HTTP_SUCCESS zurückgegeben. In der Unit mormot.core.os sind viele HTTP Status Code Konstanten definiert. Die konkrete Implementierung sieht wie folgt aus:
TFileHttpClient = class(TRestHttpClientWinHttp)
public constructor Create(const pmcServerURI: RawUtf8 = SERVER_URI; const pmcServerPort: RawUtf8 = SERVER_PORT); reintroduce; function LoadImage(pmImage: TImage; const pmcImageName: String): Boolean; function SaveImage(pmImage: TImage; const pmcImageName: String): Boolean; procedure GetAllFileNames(pmFileNames: TStrings); end;
Delphi-Quellcode:
Wem es noch nicht aufgefallen ist, ich vermeide String Bezeichner, wo es nur geht. Ich ziehe es vor, dafür Konstanten zu definieren. Macht am Anfang Mehrarbeit, spart sie aber schnell wieder ein.
function TFileHttpClient.SaveImage(pmImage: TImage; const pmcImageName: String): Boolean;
var return: RawUtf8; begin Result := False; if pmImage = Nil then Exit; //=> if pmcImageName = '' then Exit; //=> var stream: TRawByteStringStream := TRawByteStringStream.Create; try pmImage.Picture.SaveToStream(stream); if stream.Position > 0 then begin var methodName: RawUtf8 := TFileServiceFunction.Name.SaveFile + UrlEncode([TFileServiceFunction.Param.SaveFile_ImageName, StringToUtf8(pmcImageName)]); Result := (CallBackPut(methodName, stream.DataString, return) = HTTP_SUCCESS); end; finally stream.Free; end; end; Fortschrittsanzeige mit Mediator Mediatoren sind eine elegante Möglichkeit Funktionalität zu kapseln und die einfache und sichere Anwendung zu erreichen. Auch wenn das folgende Beispiel unterkomplex ist, ist der Gewinn erkennbar. Hinter der Funktion könnte sich auch ein semi-modaler Dialog mit Fortschrittsanzeige verbergen.
Delphi-Quellcode:
Die Umsetzung der Anbindung ist ein einfacher Aufruf der Funktion Prepare:
type
TWinHttpProgressGuiHelper = class(TCustomWinHttpProgressGuiHelper) private FProgressBar: TProgressBar; FProgressStepWidth: Integer; procedure InternalProgress(pmCurrentSize, pmContentLength: Cardinal); protected function DoUploadProgress(pmSender: TWinHttpApi; pmCurrentSize, pmContentLength: Cardinal): Boolean; override; procedure DoDownloadProgress(pmSender: TWinHttpApi; pmCurrentSize, pmContentLength: Cardinal); override; public constructor Create(pmProgressBar: TProgressBar; pmStepWidth: Integer = 5); reintroduce; procedure Prepare(pmRestClient: TRestHttpClientWinHttp; pmProgressMode: TWinHttpProgressMode); override; procedure Done; override; end; function TWinHttpProgressGuiHelper.DoUploadProgress(pmSender: TWinHttpApi; pmCurrentSize, pmContentLength: Cardinal): Boolean; begin Result := True; if pmCurrentSize = 0 then FProgressBar.Position := 0 else if pmCurrentSize >= pmContentLength then Done else InternalProgress(pmCurrentSize, pmContentLength); end;
Delphi-Quellcode:
Um die Fortschrittsanzeige in Aktion zu sehen, sollte ein Bild von der Größe ab 10MB gewählt werden. Wer sich die Protokolldatei des Servers ansieht, stellt fest, dass die Ausführung einer Funktion nur einen Bruchteil einer Millisekunde dauert. Bei lokaler Ausführung des Servers gibt es kaum Latenz. Bei dieser Geschwindigkeit hat es jede Fortschrittsanzeige schwer. Ihr müsst der Anzeige schon eine reelle Chance geben.
FProgressHelper.Prepare(FRestClient, whpUpload);
try FRestClient.SaveImage(Image, edtImageName.Text); finally FProgressHelper.Done; end; Zusammenfassung Der heutige Artikel war vor allem durch die Notwendigkeit zum radikalen Kürzen eine besondere Herausforderung. Bei dieser Aktion besteht immer die Gefahr, dass der rote Faden zum Verständnis verloren geht. Ich hoffe, ich habe nicht übertrieben und die Ausführungen blieben nachvollziehbar. mORMot ist gut dokumentiert. Die Hilfe umfasst mehr als 2500 Seiten. Davon enthalten die ersten ca. 650 Seiten einen sehr lesenswerten allgemeinen Teil, der Rest ist API Dokumentation. mORMot muss nicht in der IDE installierten werden! Es reicht aus, die entsprechenden Bibliothekspfade einzufügen. Es stehen viele Beispiele und ein freundliches Forum zur Verfügung. Wenn mehr Interesse an mORMot besteht, kann ich auch andere Teile in ähnlicher Weise kurz vorstellen. In der mORMot Reihe bisher veröffentlicht:
Thomas |
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |