|
Antwort |
Registriert seit: 8. Jan 2007 472 Beiträge |
#1
mORMot: Mustache Editor mit integriertem HTTP-Server zum Anzeigen von HTML Seiten30. Jul 2022, 00:41
Im fünften Artikel der mORMot Vorstellungsreihe werden zwei Komponenten der Bibliothek behandelt. Das eine sind die HTTP-Server Klassen, das andere die Mustache Template-Engine. Mustache wird als Logic-less bezeichnet, mit der Begründung: "We call it logic-less because there are no if statements, else clauses, or for loops". Es funktioniert durch die Expandierung von Tags, die Daten - keine, einzelne oder ganze Listen - in einer Vorlage repräsentieren. Mächtig ist dieses Werkzeug bei Erstellung von HTML für WebApps.
Die enthaltenen Verweise zur Dokumentation verlinken zur aktuell verfügbaren mORMot1 Hilfe. Für das Beispiel wird mORMot2 verwendet. Die Namen für Klassen und Funktionen können sich leicht unterscheiden. Im Anhang befindet sich der Sourcecode und das ausführbare Programm. Disclaimer: Der Sourcecode ist weder getestet noch optimiert. Er sollte mit Delphi ab Version 10.4 funktionieren. Die Benutzung der zur Verfügung gestellten Materialien erfolgt auf eigene Gefahr. Die Beispiel-Anwendung ist ein einfacher Editor. Je nach Compiler-Direktive wird die Komponente TMemo oder TSynEdit verwendet. Die Eingabe wird mit Hilfe des mORMot Mustache Renderers aufbereitet und über den integrierten HTTP-Server als HTML Seite im Edge-Browser ausgegeben. Mit dem Beispiel Quelltext für das Programm bekommt man:
In mORMot sind HTTP-Server und REST-Server getrennt, zwei Konzepte, die jedes für sich eigenständig genutzt werden können. Für die Implementierung eines HTTP-Server stehen die Klassen THttpServer, THttpAsyncServer und THttpApiServer zur Verfügung. Beim THttpServer handelt es sich um einen Socket-basierten Server. Er verwendet einen Thread-Pool für kurzlebige HTTP/1.0 Anfragen und einen Thread pro Verbindung bei HTTP/1.1. Der THttpAsyncServer ist vollständig ereignisgesteuert und in der Lage, Tausende von gleichzeitigen Verbindungen zu bewältigen. Ausschließlich unter Windows steht der THttpApiServer auf Basis des http.sys Modules zur Verfügung. Dieses Modul kommt auch im IIS und .NET zum Einsatz. Die Socket-basierten Server unterstützen TLS nativ. Wenn vorhanden, wird OpenSSL verwendet, andernfalls die SChannel-API von Windows. Wer sich für mehr interessiert, dem sind die beiden Artikel New Async HTTP/WebSocket Server on mORMot 2 und Native TLS Support for mORMot 2 REST or WebSockets Servers empfohlen. HTTP-Server Für unser Beispiel wird die Klasse THttpServer verwendet. Der Betriebsmodus des Servers kann mit Kommandozeilenschalter oder per Auswahldialog bestimmt werden. Zur Auswahl stehen: HTTP/1.0, HTTP/1.1, HTTPS mit selbstsignierten Zertifikat oder, wenn vorhanden, mit eigenem Zertifikat. Der Schalter für die Kommandozeile ist -sm mit den Optionen hsmHttp10|hsmHttp11|hsmHttpsSelf. Die Rückruffunktion, in der alle Prozesse auf höherer Ebene zusammenkommen, ist der Event OnRequest. Der Prototyp ist in der Unit mormot.net.http wie folgt definiert:
Delphi-Quellcode:
Über die Klasse THttpServerRequest werden Eingabeparameter übernommen und die Ausgabeargumente, wie zum Beispiel Header, Body und Statuscode, mit Inhalt befüllt, die dann als Antwort gesendet werden. Konkret, man nimmt die Eingabe-URI und zerlegt sie in ihre Teile. Hier verwenden wir Pfad (path) und Abfrage (query). Im Gegensatz zu den REST-Servern, Methoden- oder Interface-based, gibt es kein automatisches Routing und muss selbst erledigt werden. Die Selektoren im Beispiel sind "INDEX.HTML" und ".ZIP/". Mehr Spezialisierung ist nicht notwendig. Der Rest wird einer Standard Funktion zugeführt.
type
TOnHttpServerRequest = function(Ctxt: THttpServerRequestAbstract): cardinal of object;
Delphi-Quellcode:
Standard deshalb, weil in den THttp*Server Klassen ein optimiertes Verfahren für die Übertragung von Dateien implementiert ist. Der Mechanismus aktiviert sich, wenn OutContentType die Konstante STATICFILE_CONTENT_TYPE zugewiesen wird. Des Weiteren wird an OutContent der Dateinamen übergeben und zum Abschluss der passende OutCustomHeaders gewählt.
function TFileServer.DoRequest(pmCtxt: THttpServerRequestAbstract): Cardinal;
var urlPath: RawUtf8; urlQuery: RawUtf8; begin if not SplitUrlSchemePathAndQuery(pmCtxt.Url, urlPath, urlQuery) then Exit(HTTP_BADREQUEST); //=> if IdemPChar(PUtf8Char(Pointer(urlPath)), DEFAULTHTML_FILENAME) then Result := DoDefaultFileRequest(pmCtxt, urlPath) else if PosI(ZIPFILE_SEPERATOR, urlPath) > 0 then Result := DoZipFileRequest(pmCtxt, urlPath) else Result := DoFileRequest(pmCtxt, urlPath); end;
Delphi-Quellcode:
Im Artikel wird der Query-Teil einer URI nicht benutzt. Trotzdem möchte ich an einem Beispiel zeigen, wie er sich in seine einzelnen Teile zerlegen lässt. Es werden einige Low-Level-Funktionen genutzt, die für diese Aufgabe in der Bibliothek vorhanden sind. In REST-Servern steht über die Klasse TRestServerUriContext höher spezialisierte Funktionen zur Verfügung. Die Reihenfolge der Parameter ist beliebig. Besonders zu beachten ist nur die letzte UrlDecode* Funktion (ganz genau hinsehen!) mit der Übergabe der Adresse.
function TFileServer.DoFileRequest(pmCtxt: THttpServerRequestAbstract; const pmcUriPath: RawUtf8): Cardinal;
var fileName: TFileName; begin Utf8ToFileName(pmcUriPath, fileName); if CheckFileName(fileName, [fnvFileName], @fileName) then begin fileName := MakePath([Executable.ProgramFilePath, ASSETS_FOLDER, fileName]); pmCtxt.OutContent := StringToUtf8(fileName); pmCtxt.OutContentType := STATICFILE_CONTENT_TYPE; pmCtxt.OutCustomHeaders := STATICFILE_CONTENT_TYPE_HEADER + #13#10 + GetMimeContentTypeHeader('', fileName); Result := HTTP_SUCCESS; end else Result := HTTP_NOTFOUND; end;
Delphi-Quellcode:
Die Instanziierung eines HTTP-Servers ist mit wenigen Zeilen erledigt:
const
PARAMS: RawUtf8 = 'u=Text&i=10&d=8.8'; var p: PUtf8Char; u: RawUtf8; i: Integer; d: Double; begin p := PUtf8Char(Pointer(PARAMS)); if UrlDecodeNeedParameters(p, 'U,I,D') then begin repeat UrlDecodeValue(p, 'U=', u); UrlDecodeDouble(p, 'D=', d); UrlDecodeInteger(p, 'I=', i, @p); until p = Nil; ShowMessage(Utf8ToString(FormatUtf8('String: %'#13#10'Interger: %'#13#10'Double: %', [u, i, d]))); end;
Delphi-Quellcode:
Mehr ist nicht zeigbar, das war es schon. Ein HTTP/1.0 Server der auf Port 8080 mit maximal 2 Threads läuft. Zeigte ich, wie einfach TLS zu aktivieren wäre, könnten Nutzer anderer Bibliothek . Dieser Verantwortung möchte ich mich nicht stellen.
FHttpServer := THttpServer.Create({Port=} '8080', Nil, Nil, '', {ServerThreadPoolCount=} 2, {KeepAliveTimeOut=} 0);
FHttpServer.WaitStarted(WAIT_SECONDS); Mustache Template-Engine Die Template-Sprache besteht nur aus wenigen Elementen, es sind Variables, Sections und Partials. Alle Mustache Tags werden in doppelte geschweifte Klammern {{...}} eingeschlossen. Eine Variable FirstName wird somit wie folgt kodiert: {{FirstName}}. Eine Sektion repräsentiert einen geladenen Kontext. In der Praxis ein JSON Objekt oder Array. Eine Sektion vom Typ "if ..." wird mit {{#...}}, ein "if not ..." mit {{^...}} eingeleitet. Sektionen sind zwingend mit {{/...}} zu beenden. Ist der Kontext eine Liste, wird diese vom ersten bis letzten Element durchlaufen und angewandt. Die Sektion ist auch der Root für die Variablen. Beispiel: Eine Liste mit Personendaten (Objekte) soll durchlaufen und der Name (Variable) jeder Person ausgegeben werden. Lösung: {{#Persons}}{{Name}}<br>{{/Persons}}. Außerhalb einer Sektion muss für Variablen der volle Pfad angeben werden. Für den Kontext Person: {{Person.Name}}. Partials entsprechen Pascal Include-Dateien. Alles Wichtige ist hier nachzulesen: Mustache template engine. Grau ist alle Theorie - so sieht es in der Praxis aus:
Code:
Die Sprache lässt sich mit eigenen Funktionen (Expression Helpers) erweitern. Diese werden in einer Liste vom Typ TSynMustacheHelpers registriert und dem Prozessor übergeben. Ihr Aufbau ist wie folgt:
<!-- Hero is loaded from the Partials folder -->
{{>Hero}} {{#Person}} <!-- CSS class h3 is defined in Bootstrap --> <p class="h3">Person data</p> <ul> <li>Name: {{FirstName}} {{LastName}}</li> <!-- CurrToText is a self-written Expression Helper to format money output --> <li>Income: {{CurrToText Income}}</li> </ul> {{/Person}} {{^Person}} <p>If this is displayed, the Person data object was not found.</p> {{/Person}} Cheers {{Person.FirstName}}
Delphi-Quellcode:
Mit diesem Vorwissen ausgestattet, jetzt zur konkreten Implementierung in der Anwendung. Für die Anzeige im Edge-Webbrowser bauen wird die HTML Seite zusammen. Der Teil für den Mustache Renderer umfasst nur wenige Zeilen:
procedure TFileServer.CurrToText(const pmcValue: Variant; out pmoResult: Variant);
begin pmoResult := CurrToStrF(pmcValue, ffCurrency, 2); end;
Delphi-Quellcode:
Das Geniale an einem DocVariant ist, dass es eine beliebig komplexe Datenstruktur aus Objekt(en) und/oder Arrays, oder aus Kombinationen von beiden sein kann. Nur der zur Verfügung stehende Arbeitsspeicher ist die Grenze. Die Zusammenstellung der Kontext-Daten ist ein anschauliches Beispiel für die einfache Handhabung:
var
writer: TTextWriter; contextData: TDocVariantData; begin ... PrepareMustachePartials; PrepareMustacheContextData(pmcProjectData.MustacheDataFiles, contextData); writer.AddString(TSynMustache.Parse(pmcProjectData.Mustache).Render(Variant(contextData), FMustachePartials, FMustacheHelpers));
Delphi-Quellcode:
Am Ende sind alle JSON Daten in einen DocVariant geladen und werden als Paket an den Mustache Renderer übergeben. WOW.
procedure TFileServer.PrepareMustacheContextData(const pmDataFiles: TRawUtf8DynArray; out pmoContextData: TDocVariantData);
var context: TDocVariantData; contextName: RawUtf8; begin pmoContextData.Init; var assetsFolder: TFileName := MakePath([Executable.ProgramFilePath, ASSETS_FOLDER], True); var contextFiles: TFileNameDynArray := FileNames(assetsFolder, CONTEXTFILE_SEARCHMASK, [ffoExcludesDir]); for var i: Integer := 0 to High(contextFiles) do begin if FindPropName(pmDataFiles, StringToUtf8(contextFiles[i])) < 0 then Continue; //-> context.InitJsonFromFile(assetsFolder + contextFiles[i], JSON_FAST_FLOAT); contextName := StringToUtf8(GetFileNameWithoutExt(contextFiles[i])); pmoContextData.AddValue(contextName, Variant(context)); context.Clear; end; end; Der Mustache Editor: kurz und bündig
Der heutige Artikel hat erste Blicke auf einen zentralen Baustein, die Phalanx der HTTP-Server, der mORMot Bibliothek geworfen. Die enorme Leistungsfähigkeit dieser Server Klassen sind das Fundament für die Mächtigkeit der nachlaufenden RestServer. Als Werkzeug eröffnet die Mustache Template Engine, mit einer einfach zu erlernenden Sprache, die Möglichkeit, eine Vorlage und JSON Daten zur Aufbereitung zu verbinden. Sie ist Basis der Unit mormot.rest.mvc, die auf Grundlage des Model-View-Controller Patterns die Erstellung von Web-Apps ermöglicht. 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 |
Themen-Optionen | Thema durchsuchen |
Ansicht | |
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 |
LinkBack |
LinkBack URL |
About LinkBacks |