Entw.: RESTServer-Entwicklung (01)

Projektstruktur

Die IDE der Entwicklungumgebung von Embarcadero bietet die Möglichkeit, basiert auf dem gleichen Source-Stand, gleichzeitig einen StandAlone-Server sowie eine ISAPI-Version für den Internet Information Server (IIS) zu entwickeln.

Automatische ParallelentwicklungAutomatische Parallelentwicklung

Dies ist in der Praxis vom grossen Preisvorteil. Es lassen sich so Entwicklungs- und Testzeiten einspaaren, da das Arbeiten mit dem StandAlone-Server während der Entwicklungphase einfach besser zu handhaben ist. Aus Entwicklersicht ist Client und Server eine Einheit. Z.B. wird eine neue REST-Methode erstellt, so wird für den Client automatisch der entsprechende Client-Source erzeugt. Jede neue oder geänderte REST-Methode lässt sich über den sogenannten Server Function Invoker sofort schnell austesten.

Security

Im Zeitalter von Weiterleitungen und Loadbalancing ist oft eine eigene Authentifizierung zu implementieren, um eine eindeutige Identifizierung von Client und Benutzer durch den Server-Prozess sicher zustellen. Das Authentifizierungsverfahren und die Verwendung der anderen Sicherungsmassnahmen von DataSnap kann zusätzlich hinzugeschaltet werden.
Das hier vorgestellte Authentifizierungsverfahren basiert auf folgende Implementierungsdetails:

  • Die verwendete User-SessionId (USID) ist nicht interpretierbar. Jede Client-Anfrage benötigt eine gültige USID. Eine gültige USID erhält der Client nach einer erfolgreichen Authentifizierung.
  • Die Authentifizierung erfolgt mit 4 Parametern: Benutzername, Passwort, Mandant, Rolle. Zwischen Benutzernamen, Mandant und Rolle besteht eine Datenrelation (Dreiecksbeziehung)
  • IP-Adressen werden überwacht. Verwaltet werden Positiv- und Negativlisten von IP-Adressen, so dass sehr schnell auf unerlaubte Zugriffe durch die Verwaltung reagiert werden kann.
  • Bei automatisierten Angriffen werden die Remote-IP-Adressen automatisch gesperrt. Dabei ist die Sperrzeit temporär und die Dauer richtet sich nach der Anzahl von Login-Versuchen. Temporäre Sperren lassen sich jederzeit in Permanentsperren überführen.
  • Alle Anfragen von gesperrten IP-Adressen werden auf eine Null-Methode umgeleitet. Dadurch wird das Gesamtsystem des Servers entlastet.

Folgender Source zeigt hierzu die Implementierung bei der HTTP-Zugriffsart GET:

procedure THermesWebModule.DSHTTPWebDispatcherRESTMethodNameMapGET(
  Sender: TObject; const ClassCtx, MethodCtx: string; out DSMethodName: string);
var
 l_iBeg: Integer;
 l_sPar, l_sStr: String;
 l_lDoExNullMethode: Boolean;
begin
 l_lDoExNullMethode := False;
 DSMethodName := Format('%s.%s', [ClassCtx, MethodCtx]);
 CodeSite.Send('GET Methode: '  + DSMethodName);
 if not DMMainServerModule.IsValidRemoteAddr(Request.RemoteAddr) then begin
   l_lDoExNullMethode := True;
   CodeSite.Send('  Invalid RemoteAddr'  + Request.RemoteAddr);
 end;
 if MethodCtx = 'Authentification' then begin
   if not DMMainServerModule.IsExplizitValidRemoteAddr(Request.RemoteAddr) then begin
     l_sStr := String(Request.PathTranslated);
     l_iBeg := Pos(MethodCtx,l_sStr);
     l_sPar := Copy(l_sStr,l_iBeg+Length(MethodCtx)+1,999);
     if DMMainServerModule.CheckIfTooManyLogins(Request.RemoteAddr, l_sPar) then begin
       l_lDoExNullMethode := True;
       CodeSite.Send('  Too many logins by RemoteAddr: ' + Request.RemoteAddr + '; TS: '  + DateTimeToStr(Now));
     end;
   end;
 end;
 if l_lDoExNullMethode then begin
   DSMethodName := 'TDMHermesAdminMethods.NullMethode';
   CodeSite.Send('  Execute NullMethode');
 end;
end;

Auch für die HTTP-Zugriffsart PUT gibt es eine ähnliche Implementierung.

StandAlone-Server

In der Entwicklungs- und Testphase ist der StandAlone-Server von DataSnap massgebend. Es handelt sich um eine einfache Exe-Datei für einem Windows-Rechner. Nach dem Programmstart erscheint nachfolgendes Fenster.

StandAlone-ServerStandAlone-Server

Aktivieren lässt sich der eigentliche RESTServer durch dem Button Starten. Die Client-Seite lässt sich ebenfalls aus diesem Fenster durch dem Buttom Browser öffnen starten. Im geöffneten Browser erscheint die Startseite der WebApp. Zusätzlich erscheint beim lokalen Start ein Link auf den sogenannten Server Function Invoker.

Lokaler Test mit Server Function Invoker

Für den schnellen Test bietet DataSnap die Möglichkeit, neue bzw. geänderte REST-Methoden sofort zu testen. Folgender Source zeigt die Deklarationen von einigen implementierten REST-Methoden.

type
{$MethodInfo ON}
  TDMHermesDataLists = class(TDSServerModule)
  private
    { Private declarations }
    function GetJSONObjectMaengelGefahrgutwagenHeader1(const Farbe, Text1: String): TJSONObject;
    function GetJSONObjectMaengelGefahrgutwagenHeader2(const Farbe, Text1, Text2, Text3, Text4, Text5: String): TJSONObject;
    function GetJSONObjectMaengelGefahrgutwagenLine(const Farbe: String; const AnzInGroup: Integer; const Text1, Ladestelle: String; AnzKontrolliert,AnzKleineMaengel,AnzGrosseMaengel: Integer): TJSONObject;
  public
    { Public declarations }
    function WagenlisteAktuelleRAs(     // Liefert eine Wagenliste von allen aktiven Rangierauftraegen
               const USID: String;
               const SortierungNachZeit: Boolean;      // True: Sortierung nach Zeit; False: Sortierung nach Wagennummer
               const RowNoBeg, RowNoEnd: Integer
               ): TJSONObject;
    function WagenlisteRAs(             // Liefert eine Wagenliste von Rangierauftraegen
               const USID: String;
               const Wagennummer: String;              // <Leer>, '*' oder konkrete Wagennummer
               const Beladen: Boolean;                 // True oder False
               const Ladegut: String;                  // <Leer>, '*' oder z.B. '11', '07 - Polyester'
               const ZugestelltVon: String;            // <Leer>, '*' oder z.B. '2101', '2101 - Gleis xyz'
               const AbgeholtNach:  String;            // <Leer>, '*' oder z.B. '2101', '2101 - Gleis xyz'
               const SortierungNachZeit: Boolean;      // True: Sortierung nach Zeit; False: Sortierung nach Wagennummer
               const VonDatum, BisDatum: TDateTime;
               const RowNoBeg, RowNoEnd: Integer
               ): TJSONObject;
    function WagenlisteBestand(         // Liefert eine Liste des aktuellen Wagenbestands
               const USID: String;
               const Wagennummer: String;              // <Leer>, '*' oder konkrete Wagennummer
               const Beladen: Boolean;                 // True oder False
               const Ladegut: String;                  // <Leer>, '*' oder z.B. '11', '07 - Polyester'
               const Standort: String;                 // <Leer>, '*' oder z.B. '2101', '2101 - Gleis xyz'
               const SortierungNachStandort: Boolean;  // True: Sortierung nach Wagenstandort; False: Sortierung nach Wagennummer
               const RowNoBeg, RowNoEnd: Integer
               ): TJSONObject;
    function StatistikMaengelGefahrgutwagen(  // Liefert eine Aufstellung ueber Maengel an Gefahrgutwagen
               const USID: String;
               const VonDatum, BisDatum: TDateTime
               ): TJSONObject;
    function StatistikAusgang(     // Liefert eine Aufstellung der Ausgaenge in einem bestimmten Zeitraum
               const USID: String;
               const Beladen: Boolean;                 // True oder False
               const Ladegut: String;                  // <Leer>, '*' oder z.B. '11', '07 - Polyester'
               const EmpfaengerGleis: String;          // <Leer>, '*' oder z.B. '2101', '2101 - Gleis xyz'
               const VonDatum, BisDatum: TDateTime
               ): TJSONObject;
    function StatistikEingang(     // Liefert eine Aufstellung der Eingaenge in einem bestimmten Zeitraum
               const USID: String;
               const Beladen: Boolean;                 // True oder False
               const Ladegut: String;                  // <Leer>, '*' oder z.B. '11', '07 - Polyester'
               const EmpfaengerGleis: String;          // <Leer>, '*' oder z.B. '2101', '2101 - Gleis xyz'
               const VonDatum, BisDatum: TDateTime
               ): TJSONObject;
  end;
{$MethodInfo OFF}

Beim Start des RESTServers werden mit Hilfe von Reflexion die JavaScript-Aufrufe für den Client erzeugt. Diese Aufrufe werden werden vom Server Function Invoker für den lokalen Test verwendet.

Lokaler TestLokaler Test

Das rechte Bild zeigt den Browser mit der Oberfläche des Server Function Invoker's.