Teil 1) Der Serie über Server Side Dart ohne Framework

Ich mag es sehr, wenn ich die gleiche Sprache zum Schreiben von Servercode verwenden kann und gleichzeitig zum Schreiben von Anwendungen am Smartphone, Tablet, Laptop, sowie für den Linux-, Mac- und Windows-Desktop.

Flutter Apps setzen auf die Programmiersprache Dart auf. In vielen Projekten wurde für Flutter Apps auf Frameworks wie Aqueduct oder Angel zurück gegriffen.

Wir verwenden heute den Plan B, bevor wir uns Flutter Frameworks näher ansehen.

Frameworks sind zwar nett, aber sie haben immer etwas Magisches an sich.

Die dart:io-Bibliothek, eine der Kernbibliotheken von Dart, enthält bereits die Low-Level-Klassen und -Funktionen, die für die Erstellung eines HTTP-Servers benötigt werden. In diesem Artikel lernen Sie also, wie Sie selbst einen solchen Server erstellen können.

Fangen wir also an. Der vollständige Code befindet sich am Ende des Artikels, falls du dich auf dem Weg dorthin verlaufen solltest.

Es gibt viele Beschreibungen im Netz, wie du Dart und Flutter auf deiner Developer Worktstation installieren kannst. Wir gehen darauf auch in folgendem Artikel ein:

Speichere deine Arbeit und starte die Server App neu.

Die Developer Workstation
Mit PureOS und Debian 11 Sourceslistnano /etc/apt/sources.list sources.list.txt1 KB download-circle PureOSDebian -- The Universal Operating SystemThe Universal Operating System Visual Studio Code DevelopInstalliere Visualcode im Debian/PureOS Linux Terminal in zwei Minuten!…

Let's start

Während ich diesen Artikel schreibe verwende ich Dart 2.18.6.Erstelle nun ein neues Dart-Projekt zB. mit dem Namen basis_server auf der Kommandozeile:
dart create dart_server

Öffne diesen Ordner mit deiner bevorzugten IDE. Ich verwende nano im Terminal und VSCode als IDE mit dem Dart-Plugin.

Erstelle den Servers

Ersetze den Inhalt der Datei dart_server.dart durch den folgenden Code:

import 'dart:io';
Future<void> main() async {
  final server = await createServer();
  print('Server started: ${server.address} port/ ${server.port}');
  }
Future<HttpServer> createServer() async {
  final address = InternetAddress.loopbackIPv4;
  const port = 4040;
  return await HttpServer.bind(address, port);
}

Damit wird ein Server erstellt, der auf die IP-Adresse localhost (127.0.0.1) am Port 4040 hört.

Du kannst die Server App im Terminal starten mit:
dart run bin/dart_server.dart

Danach solltest du den folgenden Ausdruck sehen:
Server started: InternetAddress('127.0.0.1', IPv4) port 4040

Herzlichen Glückwunsch! Du hast bereits einen Dart-Server erstellt. Das war ziemlich einfach, nicht wahr?

Du hast die Server App neu gestartet, aber sie tut noch nicht wirklich etwas.

Mit der Tastenkombination Strg+C kannst du die Server-App wieder schließen.

Speichere deine Arbeit und starte die Server App neu.

Handhabung von HTTP-Anfragen

HTTP-Anfragen umfassen Dinge wie GET, POST, PUT und DELETE. Wenn du damit noch nicht vertraut bist, findest du weitere Informationen unter "Werde ein Backend-Entwickler".

Ersetzen Sie die Hauptfunktion durch den folgenden Code:

Future<void> main() async {
  final server = await createServer();
  print('Server started: ${server.address} port ${server.port}');
  await handleRequests(server);
}

Und füge handleRequests als Top-Level-Funktion in dart_server.dart hinzu:

Future<void> handleRequests(HttpServer server) async {
  await for (HttpRequest request in server) {
    request.response.write('Hello from a Dart server');
    await request.response.close();
  }
}
  • Notiz: Die Klasse HttpServer implementiert Stream<HttpRequest>. Das bedeutet, dass du sie wie einen Stream behandeln kannst, um die Anfragen eine nach der anderen zu bearbeiten, wenn sie eingehen.
  • Deine handleRequests() Methode ignoriert die Art der Anfrage. Sie gibt einfach auf alles die gleiche Antwort: zuerst durch das Schreiben einer Zeichenkette (die im Antwortkörper zurückgegeben wird) und dann durch das Schließen der Antwort, die sie an den Anfrager zurückschickt.

Führe das Programm erneut aus, und öffne dann folgende Adresse im Browser:

Dein Browser hat dazu eine GET-Anfrage gestellt. Als nächstes wirst du sehen, wie du verschiedene Arten von Anfragen weiterleiten kannst.

Handhabung von HTTP-Anfragen

Anstatt jede Anfrage gleich zu behandeln, sollten wir sie nach Typ weiterleiten. Ersetzen Sie die Methode handleRequests() durch folgenden Code:

Future<void> handleRequests(HttpServer server) async {
  await for (HttpRequest request in server) {
    switch (request.method) {
      case 'GET':
        handleGet(request);
        break;
      case 'POST':
        handlePost(request);
        break;
      default:
        handleDefault(request);
    }
  }
}

Jede eingehende Anfrage leitest du an eine andere Methode weiter. In den nächsten Abschnitten werden wir uns mit der Behandlung von GET, POST und allem anderen befassen.

Wir werden zuerst handleGet() implementieren, also kommentiere handlePost() und handleDefault() vorübergehend aus:

      case 'POST':
        // handlePost(request);
        break;
      default:
        // handleDefault(request);

Behandlung von GET-Anfragen

Füge den folgenden Top-Level-Code zu dart_server.dart hinzu:

var myStringStorage = 'Hello from a Dart server';
void handleGet(HttpRequest request) {
  request.response
    ..write(myStringStorage)
    ..close();
}
  • Notiz: Die globale Variable myStringStorage steht hier für eine Datenbank. Wir lesen hier aus dieser Variablen und werden im nächsten Abschnitt in sie schreiben.
  • Eine GET Anforderung sollte den Serverstatus nicht ändern, daher geben wir in unserer Antwort einfach den Wert von myStringStorage zurück.
    Speichere deine Arbeit und start die Server App neu. Öffne dann die folgende Adresse erneut im Browser:
  • http://localhost:4040/
    Du solltest dasselbe Ergebnis wie zuvor sehen:
    dart-server-10-1

Handhabung von POST-Anfragen

Der Zweck von POST-Anfragen besteht darin, dem Server neue Ressourcen hinzuzufügen.

Entferne die Kommentierung bei der handlePost() Zeile in der handleRequests() Funktion und fügen Sie folgende Top-Level-Funktion in dart_server.dart ein:

Future<void> handlePost(HttpRequest request) async {
  myStringStorage = await utf8.decoder.bind(request).join();
  request.response
    ..write('Got it. Thanks.')
    ..close();
}

Du müsst auch den folgenden Import Befehl hinzufügen:
import 'dart:convert';

  • Die erste Zeile im Textkörper von handlePost() übernimmt die eingehenden Datenpakete aus der Anfrage, konvertiert sie in UTF8 formatierte Zeichenfolgen und fügt sie zu einer einzigen Zeichenfolge zusammen.
  • Sobald Sie diese Zeichenfolge haben, verwendet die Methode sie, um den Wert der globalen Variable myStringStorage zu aktualisieren. Dies ist symbolisch für das Schreiben in eine Datenbank.
  • Da wir eigentlich nur einen vorhandenen Wert aktualisieren, anstatt einen neuen zu erstellen, wäre es vielleicht sinnvoller, die REST-API so zu definieren, dass sie PUT und nicht POST verwendet. Aber POST ist für unser Beispiel in Ordnung.
  • Es gibt noch überhaupt keine Sicherheit. Wenn Sie diesen Server ins Internet stellen, kann jeder auf der Welt myStringStorage aktualisieren.

In einem zukünftigen Artikel möchte ich über Authentifizierung und Autorisierung sprechen. Für den Moment kannst du Authentifizierung mit serverseitigem Dart studieren.

Speichere deine Arbeit und starte die Server App neu.

curlx, Postcode oder Postman

Mit deinem Browser kannst du keine POST-Anfragen stellen, daher benötigst du dafür ein anderes Tool wie curl oder curlx im Terminal oder du verwendest zum Beispiel Postcode oder Postman in der VSCode IDE. Über diese und andere Möglichkeiten, POST-Anfragen zu stellen, findest du hier im Artikel Handling GET, POST, PUT oder hier im Artikel: Postman Alternativen weitere Informationen.

Ich verwende curlx im Terminal oder Postcode in der VSCode IDE, um die POST-Anfrage zu stellen. Öffne die VSCode IDE und verwende Postcode oder führe die Schritte aus:
dart_server.dart

    1. Wähle POST als Anfrage-Art aus.
    1. Schreibe http://localhost:4040 in die Adressleiste.
    1. Wähle die Registerkarte Body.
    1. Wähle raw.
    1. Schreibe eine beliebige Zeichenfolge, z. B. Hallo.
    1. Klicke auf die Schaltfläche Send.
      Du solltest eine 200 OK Antwort vom Server mit "Ich hab's. Danke." im Body-Text erhalten:
      dart_server.dart-02
      Wenn du eine weitere GET Anforderung ausführst (entweder im Browser oder mit curlx/Postcode), solltest du den aktualisierten Wert von myStringStorage sehen:
      dart_server.dart-03
      Sehr gut! Du hast den Server erfolgreich von deinem Client aus aktualisiert.

Behandlung anderer Anfragen

Es gibt viele andere HTTP Anfragen, die Clients stellen können - wie PUT, PATCH, DELETE usw. Du kannst in deinem Router weitere Methoden hinzufügen, um diese zu behandeln. Hier wollen wir jedoch nur festlegen, dass keine anderen Anfragen auf unserem Server erlaubt sind.

Lösche die Kommentierung in der handleDefault() Zeile und füge folgende Top-Level-Methode hinzu:

void handleDefault(HttpRequest request) {
  request.response
    ..statusCode = HttpStatus.methodNotAllowed
    ..write('Unsupported request: ${request.method}.')
    ..close();
}
  • Notiz: Dieses Mal setze den Statuscode auf methodNotAllowed. Das entspricht einem Code von 405.
  • Du schreibst so eine Fehlermeldung in die Antwort und sendest sie an den Client zurück.

Speichere deine Arbeit und starte die Server App neu.

Teste es in curlx/Postcode, indem du eine PUT Anfrage sendest.
dart_server.dart-04
Du wirst folgende Antwort erhalten:
dart_server.dart-05
Gute Arbeit. Du hast einen guten Anfang gemacht, um deinen eigenen Dart Server zu bauen. Den vollständigen Code findest du weiter unten. Zunächst siehst du noch einige Schritte, die du als nächstes unternehmen solltest.

Wie geht's weiter?

Die meisten der in diesem Artikel behandelten Konzepte kannst du in der offiziellen Dart-Server-Dokumentation im Detail nachlesen. Du solltest auch einen Blick auf die http_server und auf die shelf Pakete werfen, die vom Dart-Team gepflegt werden.

Wenn du nicht nur einfache GET Anfragen für eine kleine Menge öffentlicher Daten unterstützt, gibt es ein paar größere Lücken, die gelöst werden müssen, bevor du Dart als Backend für eine echte Anwendung verwenden kannst:

  • Datenbank: Du müsst in der Lage sein, von Dart aus mit einer Datenbank zu kommunizieren, um Ressourcen zu speichern und abzurufen.
  • Authentifizierung: Du musst in der Lage sein, private Daten zu verbergen und nur autorisierten Benutzern zu erlauben, Ressourcen auf dem Server zu aktualisieren.
  • Bereitstellung: Es ist schön, wenn der Server auf deinem lokalen Rechner läuft, aber er muss schließlich auch von außen zugänglich sein.
    Auch wenn es technisch nicht notwendig ist, ist es hilfreich folgende Themen zu kennen:
  • Testen: Wenn du deinen Servercode nicht testest, kannst du nicht sicher sein, dass eine Änderung ihn nicht beschädigt.
  • Dateien: Wahrscheinlich wirst du nicht nur Zeichenketten aus der Datenbank an die Kunden zurückgeben wollen. Irgendwann wirst du auch Dateien bereitstellen müssen.
  • Gleichzeitigkeit: Wenn du einen Server mit mehr als einem Kern hast, solltest du ihn auch nutzen. Dazu musst du deinen Server auf einem anderen Isoliersystem starten.
  • CI/CD: Das manuelle Hochladen von Änderungen deines Servercode kann nach einer Weile langweilig werden. Es wäre schön, ein System einzurichten, das automatisch Tests durchführt und den Server aktualisiert, sobald es Änderungen gibt.
    Ich werde weitere Artikel zu diesen Themen schreiben, damit du lernen kannst, wie du deinen eigenen Dart Server ohnen Framework bauen kannst.

Auch wenn du später ein Framework verwendest, macht es dich produktiver wenn du zuerst gelernt hast, wie die Dinge funktionieren.

Anschließend findest du den vollständigen Code:

Hier ist der vollständige Code: