Echtzeitkommunikation mit REST und WebAPI

Was passiert, wenn wir keine P2P-Kommunikation aufbauen können, Websockets nicht unterstützt werden und wir Echtzeitkommunikation haben möchten? Richtig: Wir erstellen eine WebAPI mit REST und nutzen HTTP. Doch wie kann das aussehen?
Grundlagen HTTP und REST
Doch fangen wir von vorne an.. In einem anderen Artikel habe ich eine Anwendung zum Chatten vorgestellt. Diese erstellt eine P2P-Verbindung mittels Socket und dadurch wird eine Echtzeitkommunikation ermöglicht. In bestimmten Umfeldern, ist das nicht so einfach möglich. Entweder aufgrund von Firmenvorgaben oder der Firewall.
Die Protokolle HTTP und HTTPS sind als ausgehende Verbindung meist freigegeben. Wir könnten mit GET eine Nachricht abholen und mit POST[1] eine Nachricht versenden. Ich werde die Verben GET und POST zum Abfragen oder Senden einer Nachricht synonym verwenden.
Die Methodendefinitionen könnten folgendermaßen aussehen:
public class MyController {
private static string _message;
[HttpGet]
public string Get() {
return _message;
}
[HttpPost]
public void Post([FromBody] string xMessage) {
_message = xMessage;
}
}
Mit dieser Basis haben wir folgende Möglichkeiten:
_message
ist eine statische Variable zum Speichern der letzten Nachricht- POST überschreibt
_message
- GET liefert die zuletzt gesetzte Nachricht
Eingehende Nachricht empfangen
Da HTTP ein zustandsloses Protokoll ist, müsste ein Client regelmäßig ein GET senden, um die Nachricht zu prüfen. Ständiges Pollen würde allerdings Netzwerklast erzeugen, die wir normalerweise reduzieren möchten. Auch der Server wäre unter ständiger Last aufgrund der Anfragen. Hier kommen wir zu einem Nachteil von WebAPIs (bzw. HTTP). Im Gegensatz zu Sockets haben wir keinen Status. Wenn eine Anfrage (z.B. hier das GET) beantwortet wurde, besteht keine Verbindung mehr zum Client. Bei 3-4 Clients könnte man (je nach Server) eine Schleife verwenden. Sobald wir 100 oder 10.000 Clients haben, funktioniert das nicht mehr.
Der Vorteil bei HTTP-Anfragen ist, dass ein Timeout festgelegt werden kann. Nach dieser Zeitspanne wird eine Anfrage als abgebrochen. Normalerweise erfolgt ein Timeout, wenn der Server nicht erreichbar ist. Mit einem GET fragen wir den aktuellen Nachrichtenbestand ab. Ist eine Nachricht vorhanden, senden wir diese. Wenn keine neue Nachricht vorhanden ist, blocken wir die Anfrage so lange, bis eine Nachricht da ist.
Dafür können wir beispielsweise das AutoResetEvent verwenden. Wenn dieses Event ausgelöst wird, erfolgt automatisch ein Reset. Das bedeutet, dass bei mehreren Aufrufen von Wait jeweils nur einer freigegeben wird. Eine mögliche Lösung ist die nachfolgende:
// Static fields to hold mutex and message
private static readonly AutoResetEvent _resetEvent = new AutoResetEvent(false);
private static Message _message;
[HttpGet]
public Message Get() {
// Wait till event got raised
_resetEvent.WaitOne();
return _message;
}
[HttpPost]
public void Post([FromBody] Message xMessage) {
_message = xMessage;
// Set event so Wait()-Calls are executed
_resetEvent.Set();
}
Aktueller Stand und Fazit
Diese Lösung hat allerdings den Nachteil, dass keine direkte Kommunikation möglich ist. Gehen wir davon aus, dass beide Clients nur eine aktive GET-Anfrage haben und das _resetEvent intern eine Warteschlange verwendet. Dann können beide Clients nur abwechselnd eine Nachricht senden. Sobald ein Client 2 Nachrichten sendet, empfängt man selbst mindestens eine Nachricht.
Als nächster Schritt müsste eine Identifikation erfolgen, sodass die korrekte GET-Anfrage bearbeitet wird und der Client die Nachricht empfängt. Das werden wir uns allerdings in einem weiteren Artikel genauer ansehen. Das obige Beispiel ist ein Minimalkonzept, wie das Problem gelöst werden könnte. Im Falle einer einseitigen Kommunikation (nur ein Empfänger und x Sender) würde dieses Konzept grundsätzlich funktionieren.
Das Projekt ist auf GitHub gehostet . Ihr könnt euch den Quellcode gerne ansehen und weiterverwenden. Kommentare und Fragen könnt ihr hier oder im Repository stellen.
Habt ihr auch schon eine Echtzeit-WebAPI erstellt? Wie sind eure Erfahrung und welche Fallstricke hattet ihr?
Edit 2018-09-07: Link für das GitHub Repository angepasst.
Edit 2023-02-13: Link für das Gitlab Repository angepasst.
- [1]: GET und POST sind definierte REST-Methoden um CRUD-Funktionen (create, read, update, delete) abzubilden.