Webservices und Client Server Konzepte: Unterschied zwischen den Versionen

Aus CCWiki
Zur Navigation springen Zur Suche springen
 
(28 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 35: Zeile 35:
| POST || https://webservice.drlue.at/customer || Anlegen eines neuen Benutzers, die Benutzerdaten müssen mitgesendet werden
| POST || https://webservice.drlue.at/customer || Anlegen eines neuen Benutzers, die Benutzerdaten müssen mitgesendet werden
|}
|}
'''!!!ATTENTION!!!'''<br>'''Eine wichtige Anmerkung:''' Um eine Ressource nur in Teilen zu verändern sollte die '''Methode PATCH''' verwendet werden. Die '''PUT Methode''' ist zum Ersetzen einer bestehenden Ressource, also komplett ersetzen. Da wir das aber immer über '''PUT''' gemacht haben, lassen wirs dabei, halb so wild.<br>'''!!!ATTENTION!!!'''
<br>
<br>
Weiters soll die Repräsentation der Ressourcen unabhängig von deren eigentlicher Repräsenation z.B.: auf der Datenbank sein. Der Webservice soll es ermöglichen, die Repräsenation auszuwählen. Eine Webseite fordert die Daten beispielsweise als {{AL|HTML}}, ein Android Client als {{AL|Json}} an. Das Angeforderte Format wird über den '''HTTP''' '''Accept''' Header mittels eines '''MIME-Type''' festgelegt.
Weiters soll die Repräsentation der Ressourcen unabhängig von deren eigentlicher Repräsenation z.B.: auf der Datenbank sein. Der Webservice soll es ermöglichen, die Repräsenation auszuwählen. Eine Webseite fordert die Daten beispielsweise als {{AL|HTML}}, ein Android Client als {{AL|Json}} an. Das Angeforderte Format wird über den '''HTTP''' '''Accept''' Header mittels eines '''MIME-Type''' festgelegt.
Zeile 68: Zeile 69:
Laut Fielding die wichtigste Eigenschaft. Diese Eigenschaft wird jedoch weit nicht in allen Webservices welche sich als '''RESTful''' bezeichnen implementiert. Anbei ein sehr einfaches Beispiel welches das Prinzip beschreiben soll. Eine Kundenressource wird mittels '''GET''' Request angefordert:
Laut Fielding die wichtigste Eigenschaft. Diese Eigenschaft wird jedoch weit nicht in allen Webservices welche sich als '''RESTful''' bezeichnen implementiert. Anbei ein sehr einfaches Beispiel welches das Prinzip beschreiben soll. Eine Kundenressource wird mittels '''GET''' Request angefordert:


{{JSML|highlight='4-7'|code=
{{JSML|highlight="4-7"|code=
{
{
   id: "123",
   id: "123",
Zeile 78: Zeile 79:
}
}
}}
}}
Die Response liefert sämtliche Operationen oder weiterführende Ressource, die bei dieser Ressource zur Verfügung stehen.
Die Response liefert im {{AL|Json}} Attribut {{JSL|link}} sämtliche Operationen oder weiterführende Ressource die bei dieser Ressource zur Verfügung stehen.


=== Caching ===
=== Caching ===
HTTP Caching soll implementiert werden. Wobei jede Anfrage die nicht gestellt werden muss, die beste ist.
'''{{AL|HTTP}} Caching''' soll implementiert werden. '''Wobei jede Anfrage die nicht gestellt werden muss, die beste ist.'''
Klären wir zuerst den Begriff des Cachings. Dieses Konzept gilt natürlich in vielen Bereichen der Informatik, wir erklären den Begriff anhand des Zugriffs auf eine Ressource. Eine Ressource wird geholt, zu einem späteren Zeitpunkt wird diese Ressource erneut geholt. Hat sich diese nicht verändert, so soll sie nicht erneut übertragen werden, sondern aus dem Zwischenspeicher der Anwendung abgerufen werden.<br>
Klären wir zuerst den Begriff des '''Cachings'''. Dieses Konzept gilt natürlich in vielen Bereichen der Informatik, wir erklären den Begriff anhand des Zugriffs auf eine '''Ressource'''. Eine '''Ressource''' wird geholt, zu einem späteren Zeitpunkt wird diese '''Ressource''' erneut geholt. Hat sich diese nicht verändert, so soll sie nicht erneut übertragen werden, sondern aus dem Zwischenspeicher der Anwendung abgerufen werden.<br>
  Durch Caching bei Webanwendungen soll die Übertragung von Daten über das Internet reduziert werden.
  Durch '''Caching''' bei Webanwendungen soll die Übertragung von Daten über das Internet reduziert werden.
In folgender Tabelle wird die Funktionsweise von HTTP Caching mittels ETag beschrieben. Man beachte, die HTTP Anfragen sind ''nicht'' vollständig und beschränken sich auf die fürs Caching wesentlichen Header.
In folgender Tabelle wird die Funktionsweise von '''{{AL|HTTP}} Caching''' mittels '''ETag''' beschrieben. Man beachte, die {{AL|HTTP}} Anfragen sind '''nicht''' vollständig und beschränken sich auf die fürs '''Caching''' wesentlichen {{AL|HTTP|HTTP Header}}.
{| class="wikitable"
{| class="wikitable"
|+ Ablauf beim Anfragen auf eine Ressource (http://webservice.drlue.at/customer/101)
|+ Ablauf beim Anfragen auf eine Ressource (http://webservice.drlue.at/customer/101)
Zeile 106: Zeile 107:
}
}
}}
}}
|Erstmaliges holen der spezifischen Ressource. Server Antwortet mit den Daten und liefert im HTTP Header den ''ETag'' mit
|Erstmaliges holen der spezifischen Ressource. Der Server antwortet mit den Daten und liefert im '''HTTP Header''' den '''ETag''' mit.
|- style="vertical-align:top;"
|- style="vertical-align:top;"
|style="white-space: nowrap;"|
|style="white-space: nowrap;"|
Zeile 120: Zeile 121:
}}
}}
{{Space30}}{{Space30}}
{{Space30}}{{Space30}}
|Eine weitere Anfrage des Clients erfolgt mit dem ''ETag'' nun aber im ''If-none-match'' Header. Man könnte diese Anfrage wie folgt interpretieren: Gib mir die Ressource nur zurück, wenn der ''ETag'' nicht  mehr zutrifft. Der Server Antwortet mit dem ''HTTP Statuscode 304 Not Modified''. Dies bedeutet, es hat sich nichts geändert. Der Client wird somit darauf hingewiesen die Daten aus seinem eigenen ''Cache'' oder ''Zwischenspeicher'' zu holen.  
|Eine weitere Anfrage des Clients erfolgt mit dem '''ETag''' nun aber im '''If-none-match Header'''. Man könnte diese Anfrage wie folgt interpretieren: Gib mir die Ressource nur zurück, wenn der '''ETag''' nicht  mehr zutrifft. Der Server Antwortet mit dem '''HTTP Statuscode 304 Not Modified'''. Dies bedeutet, es hat sich nichts geändert. Der Client wird somit darauf hingewiesen die Daten aus seinem eigenen '''Cache bzw. Zwischenspeicher''' zu holen.  
|- style="vertical-align:top;"
|- style="vertical-align:top;"
|style="white-space: nowrap;"|
|style="white-space: nowrap;"|
Zeile 138: Zeile 139:
}
}
}}
}}
|Zu einem späteren Zeitpunkt holt der Client erneut die Selbe Ressource und sendet wieder den ''ETag'' mit. Die Ressource hat sich jedoch am Server geändert. Dieser sendet die Ressource dem Client zurück und sendet zusätzlich im Header den neuen ''ETag'' mit.  
|Zu einem späteren Zeitpunkt holt der Client erneut die selbe Ressource und sendet wieder den '''ETag''' mit. Die Ressource hat sich jedoch am Server geändert. Dieser sendet die Ressource dem Client zurück und sendet zusätzlich im Header den neuen '''ETag''' mit.  
|}
|}


==== Mögliche Implementierung ====
==== Mögliche Implementierung ====
In modernen Technologien wie z.B.: Express für Node.js finden sich automatische mechanismen für die Caching implementierung über ETags. Der Ablauf ist denkbar einfach. Der Server betrachtet vor dem Absenden der Antwort an den Client deren Inhalt. Es geht hier nur um den HTTP Body, die Header spielen hier keine Rolle. Aus dem Body wird eine Hashsumme berechnet und diese wird dann als ''ETag'' verwendet.
In modernen Technologien wie z.B.: '''Express''' für '''Node.js''' finden sich automatische Mechanismen für die '''Caching''' Implementierung über '''ETags'''. Der Ablauf ist denkbar einfach. Der Server betrachtet vor dem Absenden der Antwort an den Client deren Inhalt. Es geht hier nur um den '''{{AL|HTTP}} Body''', die '''Header''' spielen hier keine Rolle. Aus dem '''Body''' wird eine '''Hashsumme''' berechnet und diese wird dann als '''ETag''' verwendet.
  '''Hashsumme:''' Mittels eines Hashalgorithmus wie z.b.: (SHA256, MD5, ...) kann aus einer großen Datenmenge ein "eindeutiger" Wert berechnet werden. Bei SHA256 hat dieser Wert '''immer''' eine Länge von 256 Bit, egal wie groß der Input ist. Der Wert kann natürlich nicht eindeut sein, doch die Chance ist verschwindend gering, dass für unterschiedliche Input Daten, die selbe Hashsumme gebildet wird.
  '''Hashsumme:''' Mittels eines Hashalgorithmus wie z.b.: (SHA256, MD5, ...) kann aus einer großen Datenmenge ein "eindeutiger" Wert berechnet werden. Bei SHA256 hat dieser Wert '''immer''' eine Länge von 256 Bit, egal wie groß der Input ist. Der Wert kann natürlich nicht eindeut sein, doch die Chance ist verschwindend gering, dass für unterschiedliche Daten, die selbe Hashsumme gebildet wird.
Diese Implementierung bietet eine einfache Möglichkeit des Cachings um die Datenübertragung vom Client zum Server deutlich zu reduzieren. Dies reduziert die Netzwerklast auf Client und Serverseite.
Diese Implementierung bietet eine einfache Möglichkeit des '''Cachings''' um die Datenübertragung vom Client zum Server deutlich zu reduzieren. Dies reduziert die Netzwerklast auf Client und Serverseite.
<br><br>
<br><br>
Eine Rechenauslastung auf Seiten des Servers findet jedoch nicht statt. Der Body der Antwort muss bei jeder Antwort erstellt werden um den ''ETag'' zu generieren. Das bedeutet, der Server muss trotzdem Anfragen an die Datenbank stellen bzw. alle nötigen Berechnungen ausführen um die Antwort zu erstellen.
Eine Rechenauslastung auf Seiten des Servers findet jedoch nicht statt. Der '''Body''' der Antwort muss bei jeder Antwort erstellt werden um den '''ETag''' zu generieren. Das bedeutet, der Server muss trotzdem Anfragen an die Datenbank stellen bzw. alle nötigen Berechnungen ausführen um die Antwort zu erstellen.
<br>
<br>
Weiters ist das Bilden der Hashsumme, vorallem wenn es sich um größere Datenmengen handelt, eine Rechenintensive Operation. Ändern sich die Daten regelmäßig, so könnte man sich das bilden der Hashsumme und den daraus resultierenden mehraufwand, sparen.
Weiters ist das Bilden der '''Hashsumme''', vorallem wenn es sich um größere Datenmengen handelt, eine rechenintensive Operation. Ändern sich die Daten regelmäßig, so könnte man sich das bilden der '''Hashsumme''' und den daraus resultierenden Mehraufwand, sparen.


=== Zustandslosigkeit ===
=== Zustandslosigkeit ===
Jede Nachricht zum oder vom Webservice ist Zustandslos. Dies bedeutet, die Nachricht steht alleine für sich und beinhaltet alle nötigen Informationen damit diese bearbeitet werden kann. Das bedeutet, es besteht keine Verknüpfung zwischen den Nachrichten. Das bedeutet natürlich nicht dass es bei Requests keinen Zusammenhang geben kann. Wie z.B.: Bevor ein Benutzer eine Nachricht senden kann, benötigt er natürlich ein Profil. Somit muss der Webservice die Verbindung eines Clients und dessen Zustand auf dem Server speichern. Dies begünstigt bei Servern die '''[[Webservices_und_Client_Server_Konzepte#Skalierbarkeit|horizontale Skalierbarkeit]]'''.
Jede Nachricht zum oder vom Webservice ist '''zustandslos'''. Dies bedeutet, die Nachricht steht alleine für sich und beinhaltet alle nötigen Informationen damit diese bearbeitet werden kann. Das bedeutet, es besteht keine Verknüpfung zwischen den Nachrichten. Das bedeutet natürlich nicht dass es bei '''Requests''' keinen Zusammenhang geben kann. Wie z.B.: bevor ein Benutzer eine Nachricht senden kann, benötigt er natürlich ein Profil. Somit muss der Webservice die Verbindung eines Clients und dessen Zustand auf dem Server '''nicht''' speichern. Dies begünstigt bei Servern die '''[[Webservices_und_Client_Server_Konzepte#Skalierbarkeit|horizontale Skalierbarkeit]]'''.
Da die Nachrichten zustandslos sind, ist egal an welchen Server diese übermittelt werden. Gibt es mehrere Anwendungsserver so müssen diese natürlich die Daten untereinader synchron halten. Dies kann z.B.: über einen einzelnen Datenbank Server (oder einen Datenbank Cluster) realisiert werden.
Da die Nachrichten zustandslos sind, ist egal an welchen Server diese übermittelt werden. Gibt es mehrere '''Anwendungsserver''' so müssen diese natürlich die Daten untereinader synchron halten. Dies kann z.B.: über einen einzelnen Datenbank Server (oder einen Datenbank Cluster) realisiert werden.


==== Probleme bei der Zustandslosigkeit ====
==== Probleme bei der Zustandslosigkeit ====
Ein Problem bei der Anforderung der Zustandslosigkeit, stellt die Authentifizierung dar. Somit müsste der Client bei jeder Anfrage an den Webservice die kompletten Anmeldeinformationen mitsenden.
Ein Problem bei der Anforderung der '''Zustandslosigkeit''', stellt die '''Authentifizierung''' dar. Somit müsste der Client bei jeder Anfrage an den Webservice die kompletten Anmeldeinformationen mitsenden.
===== Mögliche komplett zustandslose Lösungen =====
===== Mögliche komplett zustandslose Lösungen =====
Eine mögliche Variante für eine komplett zustandslose Authentifizierung bietet '''Basic Auth'''. Im '''HTTP''' Header '''Authorization''' wird der Benutzername und das Passwort mitgesendet. Der Benutzername und das Passwort werden mit einem ''':''' getrennt und '''Base64''' ([[https://www.base64encode.org/ Encodieren]], [[https://www.base64decode.org/ Decodieren]]) codiert.<br>
Eine mögliche Variante für eine komplett zustandslose Authentifizierung bietet '''Basic Auth'''. Im '''{{AL|HTTP}} Header Authorization''' wird der Benutzername und das Passwort mitgesendet. Der Benutzername und das Passwort werden mit einem {{JSL|:}} getrennt und '''Base64''' codiert. ([https://www.base64encode.org/ Base64 encodieren], [https://www.base64decode.org/ Base 64 Decodieren])<br>
  '''Benutzername:''' Luke<br>
  '''Benutzername:''' Luke<br>
  '''Passwort:''' Luki<br>
  '''Passwort:''' Luki<br>
  '''Base64 Encodiert (Luke:Luki)''': THVrZTpMdWtp<br>
  '''Base64 Encodiert (Luke:Luki)''': THVrZTpMdWtp<br>
Der Request (als Beispiel ein GET Request) würde dann folgendermaßen aussehen:
Der '''Request''' (als Beispiel ein '''GET Request''') würde dann folgendermaßen aussehen:
{{HTTPML|code=
{{HTTPML|code=
GET /customer/101 HTTP/1.1
GET /customer/101 HTTP/1.1
Zeile 167: Zeile 168:
Authorization: Basic THVrZTpMdWtp
Authorization: Basic THVrZTpMdWtp
}}
}}
Hierbei handelt es sich nun um eine zustandslose Authentifizierung, da die komplette Anmeldeinformation jedesmal mitgesendet wird. Dadurch muss der Server natürlich auch bei jedem Request eine Überprüfung der Zugangsdaten vornehmen.<br>
Hierbei handelt es sich nun um eine '''zustandslose Authentifizierung''', da die komplette Anmeldeinformation bei jedem '''Request''' mitgesendet wird. Dadurch muss der Server natürlich auch bei jedem '''Request''' eine Überprüfung der Zugangsdaten vornehmen.<br>
Die '''Basic Authentifizierung''' wird normalerweise nicht bei einem initialen Request an den Server gesendet, der gebräuchliche Ablauf verhält sich in etwa wie folgt. Jedoch kann sich der Client die Authentifizierungsmethode merken, und bei späteren Requests immer die '''Basic Authentifizierung''' mitsenden:
Die '''Basic Authentifizierung''' wird normalerweise nicht bei einem initialen Request an den Server gesendet. Jedoch kann sich der Client die Authentifizierungsmethode merken, und bei späteren Requests immer die '''Basic Authentifizierung''' mitsenden. Der Standardablauf verhält sich in etwa wie folgt:


{| class="wikitable"
{| class="wikitable"
Zeile 186: Zeile 187:
}}
}}
||
||
Der intiale Request vom Client and den Server wird mit dem HTTP Statuscode 401 Unauthorized abgewiesen. Im HTTP Header WWW-Authenticate, wird die Authorisierungsmethode preisgegeben.
Der intiale '''Request''' vom Client and den Server wird mit dem '''HTTP Statuscode 401 Unauthorized''' abgewiesen. Im '''HTTP Header WWW-Authenticate''', wird die Authorisierungsmethode preisgegeben.
|-style="vertical-align:top;"
|-style="vertical-align:top;"
|
|
Zeile 228: Zeile 229:


===== Lösung über Session und Cookie =====
===== Lösung über Session und Cookie =====
Nach der Authentifizierung sendet der Server mittels des Response Headers '''Set-Cookie''' ein '''Cookie''' mit und erstellt somit eine '''Session - Sitzung'''. Das Cookie wird auf dem Server verschlüsselt und kann nicht durch den Client entschlüsselt und die Daten im Klartext gelesen werden. Es enthält einen '''Session Identifier''' welcher eine Zuordnung zur Session am Server dient. Die Session am Server kann alle möglichen Informationen enthalten, die der Identifikation des Benutzers dienen (z.B.: ID). Der Client (z.B.: Browser) verwendet nun dieses '''Cookie''' bei jedem Request und sendet es im Header '''Cookie''' mit. Um die Zuordnung zu gewährleisten muss der Server die Session selbst speichern, dies kann aber beispielsweise auch über eine Datenbankanbindung realisiert werden, somit haben mehrere Anwendungsserver die Möglichkeit eine Session zuzuordnen und zu verifizieren.<br>
Nach der Authentifizierung sendet der Server mittels des Response Headers '''Set-Cookie''' ein '''Cookie''' mit und erstellt somit eine '''Session (Sitzung)'''. Das '''Cookie''' wird auf Serverseite verschlüsselt und kann '''nicht''' durch den Client entschlüsselt und die Daten im Klartext gelesen werden. Es enthält einen '''Session Identifier''' welcher eine Zuordnung zur '''Session''' am Server dient. Die '''Session''' am Server kann alle möglichen Informationen enthalten, die der Identifikation des Benutzers dient (z.B.: ID). Der Client (z.B.: Browser) verwendet nun dieses '''Cookie''' bei jedem Request und sendet es im Header '''Cookie''' mit. Um die Zuordnung zu gewährleisten muss der Server die '''Session''' selbst speichern, dies kann aber beispielsweise auch über eine Datenbankanbindung realisiert werden, somit haben mehrere Anwendungsserver die Möglichkeit eine '''Session''' zuzuordnen und zu verifizieren.<br>
Weiters hat eine '''Session''' eine gewisse Gültigkeitsdauer. Wird die '''Session''' über eine längere Zeit nicht verwendet, so verliert diese ihre Gültigkeit.<br>
Weiters hat eine '''Session''' eine gewisse Gültigkeitsdauer. Wird die '''Session''' über eine längere Zeit nicht verwendet, so verliert diese ihre Gültigkeit.<br>
  Somit handelt es sich hier um eine '''stateful Authentication''' da Daten auf dem Server gespeichert werden müssen.
  Somit handelt es sich hierbei um eine '''stateful Authentication''' da Daten auf dem Server gespeichert werden müssen.
 
===== Token basierte Authentifizierung =====
===== Token basierte Authentifizierung =====
Eine komplett Zustandslose Authentifizierung, ist die Authentifizierung mittels '''Token'''. Ein Beispiel wäre hierfür der '''JWT Token'''. Der Mechanismus funktioniert ähnlich wie bei der Session, jedoch enthält der '''Token''' die Informationen selbst und nicht nur eine Zuordnung zu einer '''Session'''.<br>
Eine komplett '''zustandslose Authentifizierung''', ist die '''Authentifizierung''' mittels '''Token'''. Ein Beispiel hierfür ist der '''JWT Token'''. Der Mechanismus funktioniert ähnlich wie bei der {{Link|Lösung_über_Session_und_Cookie|Session}}, jedoch enthält der '''Token''' die benötigten Benutzerinformationen selbst und nicht nur eine Zuordnung zu einer '''Session'''.<br>
Meist besteht ein Authentifizierungstoken aus zwei Bestandteilen:
Meist besteht ein '''Authentifizierungstoken''' aus zwei Bestandteilen:
* '''Auth Token''' - Der eigentliche Token, welcher für die ständige Authentifizierung verwendet wird. Dieser hat meist nur eine sehr kurze Gültigkeit (z.B.: 30 Minuten).
* '''Auth Token''' - Der eigentliche '''Token''', welcher für die ständige Authentifizierung verwendet wird. Dieser hat meist nur eine sehr kurze Gültigkeit (z.B.: 30 Minuten).
* '''Refresh Token''' - Dieser Token hat meist eine sehr lange Gültigkeit und wird verwendet um den '''Auth Token''' zu aktualisieren. Bei der Aktualisierung kann beispielsweise geprüft werden, ob eine erneute Authentifizierung erfolgen darf.
* '''Refresh Token''' - Dieser '''Token''' hat meist eine sehr lange Gültigkeit und wird verwendet um den '''Auth Token''' zu aktualisieren. Bei der Aktualisierung kann beispielsweise geprüft werden, ob eine erneute '''Authentifizierung''' erfolgen darf.
  Bei einer Token basierten Authentifizierung können wir von einer '''stateless Authentication''' sprechen.
  Bei einer '''Token''' basierten '''Authentifizierung'''Lösung über Session und Cookie können wir von einer '''stateless Authentication''' sprechen.


=== Mehrschichtige Systeme ===
=== Mehrschichtige Systeme ===
Die Webservice Systeme sollen mehrschichtig aufgebaut werden, der Zugriff auf das System soll aber nur über eine Schnittstelle erfolgen. Der Aufbau kann wie folgt aussehen.
Die '''Webservice Systeme''' sollen '''mehrschichtig''' aufgebaut werden, der Zugriff auf das System soll aber nur über eine '''Schnittstelle''' erfolgen. Der Aufbau kann wie folgt aussehen.
# '''Loadbalancer''' - z.B.: '''Nginx'''
# '''Loadbalancer''' - z.B.: '''Nginx'''
# '''Mehrere Anwendungsserver''' - z.B.: '''Node.js Express''' Server
# '''Mehrere Anwendungsserver''' - z.B.: '''Node.js Express''' Server
# '''Datenbankserver''' - z.B.: '''Mongodb'''
# '''Datenbankserver''' - z.B.: '''Mongodb'''
Clients stellen die Anfragen nur an den '''Loadbalancer'''. Dieser verteilt die Anfragen gleichmäßig über alle '''Anwendungsserver''' welche die Anfrage bearbeiten und beantworten. Diese kommunizieren wenn benötigt mit dem '''Datenbankserver'''. Der Client selbst kommuniziert niemals mit dem Datenbankserver.
Clients stellen die Anfragen nur an den '''Loadbalancer'''. Dieser verteilt die Anfragen gleichmäßig über alle '''Anwendungsserver''' welche die Anfrage bearbeiten und beantworten. Diese kommunizieren wenn benötigt mit dem '''Datenbankserver'''. Der Client selbst kommuniziert niemals mit dem Datenbankserver.
[[Datei:Horizontale skalierung.png|mini|400px|none|Beispiel für ein mehrschichtiges System, wird {{Link|Horizontale_Skalierung|hier}} detailliert erklärt]]
[[Datei:Horizontale skalierung.dia.zip]]


== Beispiel ==
== Beispiel ==
Zeile 251: Zeile 255:
* '''Nachricht''' - Nachrichten sollen im Kontext von Benutzern oder Gruppen erstellt/abgefragt werden, jedoch auch als eigene Ressource abgefragt und modifiziert werden können.
* '''Nachricht''' - Nachrichten sollen im Kontext von Benutzern oder Gruppen erstellt/abgefragt werden, jedoch auch als eigene Ressource abgefragt und modifiziert werden können.
* '''Authentifizierung''' - Ein Benutzer soll sich an und abmelden können.<br>
* '''Authentifizierung''' - Ein Benutzer soll sich an und abmelden können.<br>
Folgende Auswahl der Routen stellt eine Möglichkeit dar, hat jedoch keinen absoluten Anspruch auf Richtigkeit. Dabei werden etwaige Vereinfachungen nicht verwendet (siehe Authentifizierung):
Folgende Auswahl der '''Routen''' stellt eine mögliche Lösung dar, hat jedoch keinen absoluten Anspruch auf Richtigkeit:


{| class="wikitable"
{| class="wikitable"
Zeile 293: Zeile 297:
|-
|-
| DELETE || /group/:id || Eine bestimmte Gruppe löschen
| DELETE || /group/:id || Eine bestimmte Gruppe löschen
|-
| POST || /group/ || Eine Gruppe erstellen
|-
| PUT || /group/:id || Eine Gruppe verändern
|-
|-
| PUT || /group/:id/user/:user_id || Einer bestimmten Gruppe einen Benutzer hinzufügen
| PUT || /group/:id/user/:user_id || Einer bestimmten Gruppe einen Benutzer hinzufügen
Zeile 310: Zeile 318:
| POST || /auth/:username_or_email || Authentifizierung durchführen, z.B.: Authentifizierungstoken und Benutzerdaten werden zurückgesendet
| POST || /auth/:username_or_email || Authentifizierung durchführen, z.B.: Authentifizierungstoken und Benutzerdaten werden zurückgesendet
|-
|-
| DELETE || /auth/:id || Authentifizierung ungültig machen bzw. Logout
| DELETE || /auth/:auth_id || Authentifizierung ungültig machen bzw. Logout
|}
|}


Zeile 327: Zeile 335:
* Mehr Arbeistspeicher
* Mehr Arbeistspeicher
* Schnellere Netzwerkanbindung
* Schnellere Netzwerkanbindung
* Größere und Leistungsfähigere Festplatten
* Größere und leistungsfähigere Festplatten
Diese Form der Skalierung ist die einfachste Variante um eine Anwendung leistungsfähiger zu machen.
Diese Form der Skalierung ist die einfachste Variante um eine Anwendung leistungsfähiger zu machen.


== Horizontale Skalierung ==
== Horizontale Skalierung ==
Die '''vertikale Skalierung''' ist ab einer gewissen Nutzungslast (anzahl der Benutzer) nicht mehr ausreichend. Hier kann dann '''horizontal Skaliert''' werden. Dies bedeutet, der Webservice wird über mehrere Rechner verteilt, bzw. der Anwendung werden weitere Rechner (Knoten/Nodes) hinzugefügt. Diese Art der Skalierung ist jedoch nicht für jede Anwendung geeignet, bzw. müssen Anwendung auf diese spezielle Art der Skalierung ausgelegt sein. Wie zuvor beschrieben, ist die '''Zustandslosigkeit''' ein wichtiges Prinzip von '''RESTful Webservices''', dieses Prinzip begünstigt '''horizontale Skalierung'''. Die Nachrichten sind '''selbstbeschreibend''', das bedeutet Sie können von jedem beliebigen Node des Webservices bearbeitet werden.<br>
Die {{Link|Vertikale_Skalierung|vertikale Skalierung}} ist ab einer gewissen Nutzungslast (anzahl der Benutzer) nicht mehr ausreichend. Hier kann dann '''horizontal Skaliert''' werden. Dies bedeutet, der Webservice wird über mehrere Rechner verteilt, bzw. der Anwendung werden weitere Rechner (Knoten/Nodes) hinzugefügt. Diese Art der '''Skalierung''' ist jedoch nicht für jede Anwendung geeignet, bzw. müssen Anwendung auf diese spezielle Art der '''Skalierung''' ausgelegt sein. Wie zuvor beschrieben, ist die {{Link|Zustandslosigkeit}} ein wichtiges '''Prinzip''' von '''RESTful Webservices''', dieses '''Prinzip''' begünstigt die '''horizontale Skalierung'''. Die Nachrichten sind '''selbstbeschreibend''', das bedeutet Sie können von jedem beliebigen Node des Webservices bearbeitet werden.<br>
Folgende Grafik soll die Funktionsweise von horizontaler Skalierung besser erklären:
Folgende Grafik soll die Funktionsweise von horizontaler Skalierung besser erklären:
[[Datei:Horizontale skalierung.png|mini|400px|none|Beispiel für horizontale Skalierung]]
[[Datei:Horizontale skalierung.png|mini|400px|none|Beispiel für horizontale Skalierung]]
Zeile 337: Zeile 345:
=== Loadbalancer ===
=== Loadbalancer ===
Der '''Loadbalancer''' verteilt alle Anfragen der Clients nach einem gewissen Algorithmus. Das kann beispielsweise ein '''Nginx''' Webserver sein.
Der '''Loadbalancer''' verteilt alle Anfragen der Clients nach einem gewissen Algorithmus. Das kann beispielsweise ein '''Nginx''' Webserver sein.
  Ein möglicher Algorithmus zur Lastverteilung, wäre '''Round Robin''' :-D. Die Lastverteilung geht einfach schön der Reihe nach. Server A, B, C, D, A, B, C, D, ...
  Ein möglicher Algorithmus zur Lastverteilung wäre '''Round Robin''' :-D. Die Lastverteilung geht einfach schön der Reihe nach. Server A, B, C, D, A, B, C, D,...


=== Anwendungsserver ===
=== Anwendungsserver ===
Node.js 1 - Node.js 4 sind die Anwendungsserver. Diese kommunizieren mit den Datenbankservern. Werden beispielsweise Bilder hochgeladen, so sollten diese Anwendungsserver auch alle zugriff auf einen gemeinsam genutzen persistenten Speicher haben - '''Shared Storage'''.
'''Node.js 1''' bis '''Node.js 4''' sind die '''Anwendungsserver'''. Diese kommunizieren mit den '''Datenbankservern'''. Werden beispielsweise Bilder hochgeladen, so sollten diese Anwendungsserver auch alle zugriff auf einen gemeinsam genutzen persistenten Speicher haben - '''Shared Storage'''.
=== Datenbankserver ===
=== Datenbankserver ===
MongoDB Shard 1 und MongoDB Shard 2, sind die Datenbankserver. Die horizontale Skalierung erfolgt bei '''MongoDB''' über sogenannte '''Shards''' (Scherben). Nicht jeder Datenbankserver verfügt über den selben Datenbestand. Ein vereinfachtes Beispiel wäre: Alle Kundendaten deren Vornamen dem Buchstaben A-M beginnt, liegen auf Shard 1, von N-Z liegen auf Shard 2.<br><br>
'''MongoDB Shard 1''' und '''MongoDB Shard 2''', sind die Datenbankserver. Die '''horizontale Skalierung''' erfolgt bei '''MongoDB''' über sogenannte '''Shards''' (Scherben). Nicht jeder Datenbankserver verfügt über den selben Datenbestand. Ein vereinfachtes Beispiel wäre: Alle Kundendaten deren Vornamen mit dem Buchstaben A-M beginnt, liegen auf '''Shard 1''', von N-Z liegen auf '''Shard 2'''. Das Beispiel macht natürlich wenig Sinn. Oft erfolgt eine verteilung über die Regionalität. Z.b.: Alle Anwender aus Österreicht auf '''Shard 1''', alle aus Deutschland auf '''Shard 2'''.<br><br>
Die Grafik ist stark vereinfacht. Müssten beim Anwendungsserver weiters '''Mongo Router''' vorhanden sein. Diese Router entscheiden welche Abfrage an welchen MongoDB Server geleitet wird. Weiters bestehen die '''Shards''' aus einem '''Replication Set''', dies ermöglicht '''Hochverfügbarkeit''' und '''hohe Datensicherheit'''.
Die Grafik ist stark vereinfacht. Es müssten beim Anwendungsserver weiters '''Mongo Router''' vorhanden sein. Diese '''Router''' entscheiden welche Abfrage an welchen '''MongoDB Server''' geleitet wird. Weiters sollten die '''Shards''' aus einem '''Replication Set''' bestehen, dies ermöglicht '''Hochverfügbarkeit''' und '''hohe Datensicherheit''', da das '''Replication Set''' aus mehreren '''synchron''' gehaltenen '''MongoDB Datenbank Servern''' besteht.
[[Datei:Sharded-cluster-production-architecture.png|mini|none|Mongo Shard<ref>https://docs.mongodb.com/manual/sharding/</ref>]]
[[Datei:Sharded-cluster-production-architecture.png|mini|none|Mongo Shard<ref>https://docs.mongodb.com/manual/sharding/</ref>]]



Aktuelle Version vom 23. Februar 2021, 17:11 Uhr

In den folgenden Abschnitten soll geklärt werden, was ein Webservice ist und worum es sich im speziellen bei RESTful Webservices handelt.

Webservices

Zu allererst soll geklärt werden, was ein Webservices ist:

Ein Webservice (auch Webdienst) stellt eine Schnittstelle für die Maschine-zu-Maschine- oder Anwendungs-Kommunikation über Rechnernetze wie das Internet zur Verfügung. Dabei werden Daten ausgetauscht und auf  entfernten Computern (Servern) Funktionen aufgerufen. Jeder Webservice besitzt einen Uniform Resource Identifier (URI), über den er eindeutig identifizierbar ist, sowie je nach Implementierung eine Schnittstellenbeschreibung in maschinenlesbarem Format (als XML-Artefakt, z. B. WSDL), die definiert, wie mit dem Webservice zu interagieren ist. Die Kommunikation kann über Protokolle aus dem Internetkontext wie HTTP oder HTTPS erfolgen; über diese Protokolle wiederum kann beispielsweise XML oder JSON übertragen werden. Ein Webservice ist plattformunabhängig und steht in der Regel mehreren Programmen zum Aufrufen bereit. [1]

Was bedeutet dies nun ganz stark vereinfacht. Ein Webservice ist ein Programm, welches auf einem Server läuft, der über das Internet erreichbar ist. Ein Client (Browser, Anwendung, App), sendet Anfragen an diesen Webservice mit einem standardisierten Protokoll (z.B.: HTTP) und erhält darauf Antworten.

Im folgenden wollen wir uns nun mit RESTful Webservices beschäftigen, diese erfreuen sich großer Beliebtheit und stehen in Konkurrenz zu anderen Varianten wie z.b.:

  • WSDL/Soap basierten Webservices - Stark antiquiertes Konezpt
  • GraphQL basierten Webservices - Damit werden unzulänglichkeiten bei RESTful Webservices behoben, jedoch wird auf eine einheitliche Schnittstelle zu gunsten der Flexibilität verzichtet

RESTful Webservice

Ein RESTful Webservice ist nicht eine konkrete Implementierung, es ist vielmehr ein Architekturstil dem gefolgt werden soll. Dieser besteht aus Prinzipien und Eigenschaften die auf einen Webservice zutreffen sollen, damit es sich um einen RESTful Webservice handelt bzw. einen Webservice der auf den REST Prinzipien beruht. REST steht für REpresential State T'ransfer und wurde von Roy Fielding entworfen.

Prinzipien

Im folgenden wollen wir auf einige der Prinzipien eingehen, jedoch in keiner vollständigen Form, es soll aber an dieser Stelle ausreichen.

Einheitliche Schnittstelle

Dies ist ein Hauptunterscheidungsmerkmal zu den meisten anderen Webservice Architekturen. Das Ziel ist, wie der Name schon sagt, eine einheitliche Schnittstelle für Webservices und eine daraus folgende einfache Nutzbarkeit zu erreichen.
Jede Ressource hat einen eindeutigen Bezeichner, URI Uniform Resource Identifier, und ist über eine URL erreichbar ist. Eine Ressource kann z.B. ein Kundenprofil sein.

Kunde mit der id 101 -> https://webservice.drlue.at/customer/101

Die Manipulation soll einheitlich erfolgen, dafür bieten sich die HTTP Methoden an. Betrachten wir das Beispiel des Kunden mit der Id 101 und stellen den Vergleich zu den HTTP Methoden her:

HTTP Methode URL Operation
GET https://webservice.drlue.at/customer/101 Holen der Benutzerinformation
DELETE https://webservice.drlue.at/customer/101 Löschen des Benutzers
PUT https://webservice.drlue.at/customer/101 Verändern des Benutzers, es müssen die zu ändernden Daten mitgesendet werden
POST https://webservice.drlue.at/customer Anlegen eines neuen Benutzers, die Benutzerdaten müssen mitgesendet werden
!!!ATTENTION!!!
Eine wichtige Anmerkung: Um eine Ressource nur in Teilen zu verändern sollte die Methode PATCH verwendet werden. Die PUT Methode ist zum Ersetzen einer bestehenden Ressource, also komplett ersetzen. Da wir das aber immer über PUT gemacht haben, lassen wirs dabei, halb so wild.
!!!ATTENTION!!!


Weiters soll die Repräsentation der Ressourcen unabhängig von deren eigentlicher Repräsenation z.B.: auf der Datenbank sein. Der Webservice soll es ermöglichen, die Repräsenation auszuwählen. Eine Webseite fordert die Daten beispielsweise als HTML, ein Android Client als Json an. Das Angeforderte Format wird über den HTTP Accept Header mittels eines MIME-Type festgelegt.

Der MIME-Type (Multipurpose Internet Mail Extensions type) oder Mediatype besteht aus folgendem Format: type/subtype.
Weiters können mehrere mögliche Mediatypes angegeben und mit einer Gewichtung versehen werden (was wäre mir das liebste Format).

Anbei ein Beispiel wie dies in Node.js mit dem Express Framework realisiert werden kann.

app.get('/user/:id', async (req, res) => {
  var user = User.findById(req.params.id);
  if(req.accepts('html')) {
    //HTML rendern wenn html akzeptiert wird
    res.render('userprofile', { user: user });
  } else {
    //Wenn html nicht akzeptiert wird, wird json zurückgegeben
    res.json(user);
  }
}
Beispiele für den Accept Header
Accept Header Bedeutung
application/json JSON wird zurückgegeben
text/html HTML wird zurückgegeben
image/png Bild wird als PNG angefordert
image/jpeg Bild wird als JPEG angefordert

Hypermedia as the Engine of Application State (HATEOAS)

Laut Fielding die wichtigste Eigenschaft. Diese Eigenschaft wird jedoch weit nicht in allen Webservices welche sich als RESTful bezeichnen implementiert. Anbei ein sehr einfaches Beispiel welches das Prinzip beschreiben soll. Eine Kundenressource wird mittels GET Request angefordert:

{
  id: "123",
  name: "Luke",
  link: {
    messages: "/user/123/messages",
    ban: "/user/123/ban" 
  }
}

Die Response liefert im Json Attribut link sämtliche Operationen oder weiterführende Ressource die bei dieser Ressource zur Verfügung stehen.

Caching

HTTP Caching soll implementiert werden. Wobei jede Anfrage die nicht gestellt werden muss, die beste ist. Klären wir zuerst den Begriff des Cachings. Dieses Konzept gilt natürlich in vielen Bereichen der Informatik, wir erklären den Begriff anhand des Zugriffs auf eine Ressource. Eine Ressource wird geholt, zu einem späteren Zeitpunkt wird diese Ressource erneut geholt. Hat sich diese nicht verändert, so soll sie nicht erneut übertragen werden, sondern aus dem Zwischenspeicher der Anwendung abgerufen werden.

Durch Caching bei Webanwendungen soll die Übertragung von Daten über das Internet reduziert werden.

In folgender Tabelle wird die Funktionsweise von HTTP Caching mittels ETag beschrieben. Man beachte, die HTTP Anfragen sind nicht vollständig und beschränken sich auf die fürs Caching wesentlichen HTTP.

Ablauf beim Anfragen auf eine Ressource (http://webservice.drlue.at/customer/101)
Anfrage Antwort Beschreibung
GET /customer/101 HTTP/1.1
Host: webservice.drlue.at

                                                                      

HTTP/1.1 200 OK
Etag: "478fb2348f700"

{
  "name": "luke",
  "id": 101
}
Erstmaliges holen der spezifischen Ressource. Der Server antwortet mit den Daten und liefert im HTTP Header den ETag mit.
GET /customer/101 HTTP/1.1
Host: webservice.drlue.at
If-none-match: "478fb2348f700"
HTTP/1.1 304 Not Modified
Etag: "478fb2348f700"

                                                            

Eine weitere Anfrage des Clients erfolgt mit dem ETag nun aber im If-none-match Header. Man könnte diese Anfrage wie folgt interpretieren: Gib mir die Ressource nur zurück, wenn der ETag nicht mehr zutrifft. Der Server Antwortet mit dem HTTP Statuscode 304 Not Modified. Dies bedeutet, es hat sich nichts geändert. Der Client wird somit darauf hingewiesen die Daten aus seinem eigenen Cache bzw. Zwischenspeicher zu holen.
GET /customer/101 HTTP/1.1
Host: webservice.drlue.at
If-none-match: "478fb2348f700"
HTTP/1.1 200 OK
Etag: "497fb2348f705"

{
  "name": "luke1",
  "id": 101
}
Zu einem späteren Zeitpunkt holt der Client erneut die selbe Ressource und sendet wieder den ETag mit. Die Ressource hat sich jedoch am Server geändert. Dieser sendet die Ressource dem Client zurück und sendet zusätzlich im Header den neuen ETag mit.

Mögliche Implementierung

In modernen Technologien wie z.B.: Express für Node.js finden sich automatische Mechanismen für die Caching Implementierung über ETags. Der Ablauf ist denkbar einfach. Der Server betrachtet vor dem Absenden der Antwort an den Client deren Inhalt. Es geht hier nur um den HTTP Body, die Header spielen hier keine Rolle. Aus dem Body wird eine Hashsumme berechnet und diese wird dann als ETag verwendet.

Hashsumme: Mittels eines Hashalgorithmus wie z.b.: (SHA256, MD5, ...) kann aus einer großen Datenmenge ein "eindeutiger" Wert berechnet werden. Bei SHA256 hat dieser Wert immer eine Länge von 256 Bit, egal wie groß der Input ist. Der Wert kann natürlich nicht eindeut sein, doch die Chance ist verschwindend gering, dass für unterschiedliche Daten, die selbe Hashsumme gebildet wird.

Diese Implementierung bietet eine einfache Möglichkeit des Cachings um die Datenübertragung vom Client zum Server deutlich zu reduzieren. Dies reduziert die Netzwerklast auf Client und Serverseite.

Eine Rechenauslastung auf Seiten des Servers findet jedoch nicht statt. Der Body der Antwort muss bei jeder Antwort erstellt werden um den ETag zu generieren. Das bedeutet, der Server muss trotzdem Anfragen an die Datenbank stellen bzw. alle nötigen Berechnungen ausführen um die Antwort zu erstellen.
Weiters ist das Bilden der Hashsumme, vorallem wenn es sich um größere Datenmengen handelt, eine rechenintensive Operation. Ändern sich die Daten regelmäßig, so könnte man sich das bilden der Hashsumme und den daraus resultierenden Mehraufwand, sparen.

Zustandslosigkeit

Jede Nachricht zum oder vom Webservice ist zustandslos. Dies bedeutet, die Nachricht steht alleine für sich und beinhaltet alle nötigen Informationen damit diese bearbeitet werden kann. Das bedeutet, es besteht keine Verknüpfung zwischen den Nachrichten. Das bedeutet natürlich nicht dass es bei Requests keinen Zusammenhang geben kann. Wie z.B.: bevor ein Benutzer eine Nachricht senden kann, benötigt er natürlich ein Profil. Somit muss der Webservice die Verbindung eines Clients und dessen Zustand auf dem Server nicht speichern. Dies begünstigt bei Servern die horizontale Skalierbarkeit. Da die Nachrichten zustandslos sind, ist egal an welchen Server diese übermittelt werden. Gibt es mehrere Anwendungsserver so müssen diese natürlich die Daten untereinader synchron halten. Dies kann z.B.: über einen einzelnen Datenbank Server (oder einen Datenbank Cluster) realisiert werden.

Probleme bei der Zustandslosigkeit

Ein Problem bei der Anforderung der Zustandslosigkeit, stellt die Authentifizierung dar. Somit müsste der Client bei jeder Anfrage an den Webservice die kompletten Anmeldeinformationen mitsenden.

Mögliche komplett zustandslose Lösungen

Eine mögliche Variante für eine komplett zustandslose Authentifizierung bietet Basic Auth. Im HTTP Header Authorization wird der Benutzername und das Passwort mitgesendet. Der Benutzername und das Passwort werden mit einem : getrennt und Base64 codiert. (Base64 encodieren, Base 64 Decodieren)

Benutzername: Luke
Passwort: Luki
Base64 Encodiert (Luke:Luki): THVrZTpMdWtp

Der Request (als Beispiel ein GET Request) würde dann folgendermaßen aussehen:

GET /customer/101 HTTP/1.1
Host: webservice.drlue.at
Authorization: Basic THVrZTpMdWtp

Hierbei handelt es sich nun um eine zustandslose Authentifizierung, da die komplette Anmeldeinformation bei jedem Request mitgesendet wird. Dadurch muss der Server natürlich auch bei jedem Request eine Überprüfung der Zugangsdaten vornehmen.
Die Basic Authentifizierung wird normalerweise nicht bei einem initialen Request an den Server gesendet. Jedoch kann sich der Client die Authentifizierungsmethode merken, und bei späteren Requests immer die Basic Authentifizierung mitsenden. Der Standardablauf verhält sich in etwa wie folgt:

Austausch der Authentifizierungsmethode
Anfrage Antwort Beschreibung
GET /customer/101 HTTP/1.1
Host: webservice.drlue.at
HTTP/1.1 401 Unauthorized
WWW-Authorization: Basic realm="DrlueWebservice"

Der intiale Request vom Client and den Server wird mit dem HTTP Statuscode 401 Unauthorized abgewiesen. Im HTTP Header WWW-Authenticate, wird die Authorisierungsmethode preisgegeben.

GET /customer/101 HTTP/1.1
Host: webservice.drlue.at
Authentication: Basic THVrZTpMdWtp

                                                                                          

HTTP/1.1 200 OK

{
  "name": "luke1",
  "id": 101
}

                                                                                                              

Der Client sendet die Authentifizierungsdaten mit, der Server kann die Authentifizierung vornehmen.

GET /customer/102 HTTP/1.1
Host: webservice.drlue.at
Authentication: Basic THVrZTpMdWtp
HTTP/1.1 200 OK

{
  "name": "luki",
  "id": 102
}

Bei jedem weiteren Request kann der Client direkt die Authentifizierungsdaten mitsenden.

Nach der Authentifizierung sendet der Server mittels des Response Headers Set-Cookie ein Cookie mit und erstellt somit eine Session (Sitzung). Das Cookie wird auf Serverseite verschlüsselt und kann nicht durch den Client entschlüsselt und die Daten im Klartext gelesen werden. Es enthält einen Session Identifier welcher eine Zuordnung zur Session am Server dient. Die Session am Server kann alle möglichen Informationen enthalten, die der Identifikation des Benutzers dient (z.B.: ID). Der Client (z.B.: Browser) verwendet nun dieses Cookie bei jedem Request und sendet es im Header Cookie mit. Um die Zuordnung zu gewährleisten muss der Server die Session selbst speichern, dies kann aber beispielsweise auch über eine Datenbankanbindung realisiert werden, somit haben mehrere Anwendungsserver die Möglichkeit eine Session zuzuordnen und zu verifizieren.
Weiters hat eine Session eine gewisse Gültigkeitsdauer. Wird die Session über eine längere Zeit nicht verwendet, so verliert diese ihre Gültigkeit.

Somit handelt es sich hierbei um eine stateful Authentication da Daten auf dem Server gespeichert werden müssen.
Token basierte Authentifizierung

Eine komplett zustandslose Authentifizierung, ist die Authentifizierung mittels Token. Ein Beispiel hierfür ist der JWT Token. Der Mechanismus funktioniert ähnlich wie bei der Session, jedoch enthält der Token die benötigten Benutzerinformationen selbst und nicht nur eine Zuordnung zu einer Session.
Meist besteht ein Authentifizierungstoken aus zwei Bestandteilen:

  • Auth Token - Der eigentliche Token, welcher für die ständige Authentifizierung verwendet wird. Dieser hat meist nur eine sehr kurze Gültigkeit (z.B.: 30 Minuten).
  • Refresh Token - Dieser Token hat meist eine sehr lange Gültigkeit und wird verwendet um den Auth Token zu aktualisieren. Bei der Aktualisierung kann beispielsweise geprüft werden, ob eine erneute Authentifizierung erfolgen darf.
Bei einer Token basierten AuthentifizierungLösung über Session und Cookie können wir von einer stateless Authentication sprechen.

Mehrschichtige Systeme

Die Webservice Systeme sollen mehrschichtig aufgebaut werden, der Zugriff auf das System soll aber nur über eine Schnittstelle erfolgen. Der Aufbau kann wie folgt aussehen.

  1. Loadbalancer - z.B.: Nginx
  2. Mehrere Anwendungsserver - z.B.: Node.js Express Server
  3. Datenbankserver - z.B.: Mongodb

Clients stellen die Anfragen nur an den Loadbalancer. Dieser verteilt die Anfragen gleichmäßig über alle Anwendungsserver welche die Anfrage bearbeiten und beantworten. Diese kommunizieren wenn benötigt mit dem Datenbankserver. Der Client selbst kommuniziert niemals mit dem Datenbankserver.

Beispiel für ein mehrschichtiges System, wird hier detailliert erklärt

Datei:Horizontale skalierung.dia.zip

Beispiel

Ein Messenger Dienst soll konzipiert werden. Folgende Ressourcen bzw. Funktionalitäten sollen abgebildet werden:

  • Benutzer - Ein Benutzer kann erstellt/verändert/gelöscht und es können ihm Nachrichten gesendet werden. Weiters kann dieser Benutzer in Gruppen enthalten sein.
  • Gruppe - Eine Gruppe enthält mehrere Benutzer, kann erstellt/verändert/gelöscht und es können ihr Nachrichten zugesendet werden.
  • Nachricht - Nachrichten sollen im Kontext von Benutzern oder Gruppen erstellt/abgefragt werden, jedoch auch als eigene Ressource abgefragt und modifiziert werden können.
  • Authentifizierung - Ein Benutzer soll sich an und abmelden können.

Folgende Auswahl der Routen stellt eine mögliche Lösung dar, hat jedoch keinen absoluten Anspruch auf Richtigkeit:

Routen für Benutzer
HTTP Methode Route Beschreibung
GET /user/:id Holen von Benutzerdaten eines bestimmten Benutzers
PUT /user/:id Verändern von Benutzerdaten eines bestimmten Benutzers
DELETE /user/:id Löschen eines bestimmten Benutzerprofils
POST /user Benutzerprofil erstellen
POST /user/:id/message Nachricht an einen bestimmten Benutzer senden
GET /user/:id/message Nachrichten für einen bestimmten Benutzer holen
GET /user/:id/group Alle Gruppen eines bestimmten Benutzers holen
Routen für Nachricht
HTTP Methode Route Beschreibung
GET /message/:id Eine bestimmte Nachricht holen
DELETE /message/:id Eine bestimmte Nachricht löschen
PUT /message/:id Eine bestimmte Nachricht verändern
Routen für Gruppe
HTTP Methode Route Beschreibung
GET /group/:id Eine bestimmte Gruppe holen
DELETE /group/:id Eine bestimmte Gruppe löschen
POST /group/ Eine Gruppe erstellen
PUT /group/:id Eine Gruppe verändern
PUT /group/:id/user/:user_id Einer bestimmten Gruppe einen Benutzer hinzufügen
DELETE /group/:id/user/:user_id Einen bestimmten Benutzer aus einer bestimmten Gruppe löschen
GET /group/:id/message Alle Nachrichten aus einer bestimmten Gruppe holen
POST /group/:id/message Eine Nachricht an eine bestimmte Gruppe senden
Routen für Authentifizierung
HTTP Methode Route Beschreibung
POST /auth/:username_or_email Authentifizierung durchführen, z.B.: Authentifizierungstoken und Benutzerdaten werden zurückgesendet
DELETE /auth/:auth_id Authentifizierung ungültig machen bzw. Logout

Skalierbarkeit

Unter Skalierbarkeit versteht man die Fähigkeit eines Systems, Netzwerks oder Prozesses zur Größenveränderung. Meist wird dabei die Fähigkeit des Systems zum Wachstum bezeichnet.[1]

Skalierbarkeit hat nicht nur bei Softwaresystemen eine Bedeutung. Betrachten wir z.B.: eine Bar. Unter der Woche kommt nicht viel Kundschaft, es wird nur eine Person zum ausschenken benötigt. Am Wochenende kommen mehr Leute und das Personal wird erhöht. Wenn die Besucherzahl ansteigt, so werden mehr Leute beschäftigt. Dies klappt aber nur bis zu einem gewissen Punkt, da der Platz beschränkt ist. Ist der Platz voll, so könnte die Barbesitzerin ihr Lokal um einen Raum erweitern, oder gar ein zweites Lokal eröffnen.
Hier können wir nun einen Vergleich zu den zwei skalierungsmöglichkeiten bei Systemen aufstellen, Vertikale Skalierung und Horizontale Skalierung.

  • Beim Aufstocken des Personals und der erweiterung der Bar um einen Raum, können wir von vertikaler Skalierung sprechen.
  • Macht die Betreiberin eine weitere Bar auf, so wäre das horizontale Skalierung

Nun wollen wir das konstruierte Beispiel verlassen und uns auf Softwaresysteme beschränken. Als Beispiel verwenden wir einen Webservice bei dem Kunden Ihre Bilder verwalten können. Steigt die Anzahl der Kunden, so haben wir wie beim Bar Beispiel zwei möglichkeiten der Skalierung.

Vertikale Skalierung

Bei der vertikalen Skalierung wird die Serverperformance eines Servers erhöht:

  • Mehr CPUs
  • Mehr Arbeistspeicher
  • Schnellere Netzwerkanbindung
  • Größere und leistungsfähigere Festplatten

Diese Form der Skalierung ist die einfachste Variante um eine Anwendung leistungsfähiger zu machen.

Horizontale Skalierung

Die vertikale Skalierung ist ab einer gewissen Nutzungslast (anzahl der Benutzer) nicht mehr ausreichend. Hier kann dann horizontal Skaliert werden. Dies bedeutet, der Webservice wird über mehrere Rechner verteilt, bzw. der Anwendung werden weitere Rechner (Knoten/Nodes) hinzugefügt. Diese Art der Skalierung ist jedoch nicht für jede Anwendung geeignet, bzw. müssen Anwendung auf diese spezielle Art der Skalierung ausgelegt sein. Wie zuvor beschrieben, ist die Zustandslosigkeit ein wichtiges Prinzip von RESTful Webservices, dieses Prinzip begünstigt die horizontale Skalierung. Die Nachrichten sind selbstbeschreibend, das bedeutet Sie können von jedem beliebigen Node des Webservices bearbeitet werden.
Folgende Grafik soll die Funktionsweise von horizontaler Skalierung besser erklären:

Beispiel für horizontale Skalierung

Datei:Horizontale skalierung.dia.zip

Loadbalancer

Der Loadbalancer verteilt alle Anfragen der Clients nach einem gewissen Algorithmus. Das kann beispielsweise ein Nginx Webserver sein.

Ein möglicher Algorithmus zur Lastverteilung wäre Round Robin :-D. Die Lastverteilung geht einfach schön der Reihe nach. Server A, B, C, D, A, B, C, D,...

Anwendungsserver

Node.js 1 bis Node.js 4 sind die Anwendungsserver. Diese kommunizieren mit den Datenbankservern. Werden beispielsweise Bilder hochgeladen, so sollten diese Anwendungsserver auch alle zugriff auf einen gemeinsam genutzen persistenten Speicher haben - Shared Storage.

Datenbankserver

MongoDB Shard 1 und MongoDB Shard 2, sind die Datenbankserver. Die horizontale Skalierung erfolgt bei MongoDB über sogenannte Shards (Scherben). Nicht jeder Datenbankserver verfügt über den selben Datenbestand. Ein vereinfachtes Beispiel wäre: Alle Kundendaten deren Vornamen mit dem Buchstaben A-M beginnt, liegen auf Shard 1, von N-Z liegen auf Shard 2. Das Beispiel macht natürlich wenig Sinn. Oft erfolgt eine verteilung über die Regionalität. Z.b.: Alle Anwender aus Österreicht auf Shard 1, alle aus Deutschland auf Shard 2.

Die Grafik ist stark vereinfacht. Es müssten beim Anwendungsserver weiters Mongo Router vorhanden sein. Diese Router entscheiden welche Abfrage an welchen MongoDB Server geleitet wird. Weiters sollten die Shards aus einem Replication Set bestehen, dies ermöglicht Hochverfügbarkeit und hohe Datensicherheit, da das Replication Set aus mehreren synchron gehaltenen MongoDB Datenbank Servern besteht.

Mongo Shard[2]

Weitere Quellen

[3] [4] [5] [6]