Echte Katalogdaten zurückgeben

GolangBeginner
Jetzt üben

Einführung

Ein Fundbüro im Stadtviertel besitzt eine kleine Sammlung von Fundsachen. Die vorbereitete Webseite kann Gegenstände anzeigen, doch die Beispiel-API liefert nur einen Datensatz. Du verbindest echte Go-Datensätze mit Listen- und Detailantworten, damit Besucher den richtigen Abholort finden.

Du solltest Funktionen, Strukturen, Slices, Maps und Module in Go sowie grundlegende Terminalpfade bereits kennen. HTTP, JSON und Handler-Tests werden hier eingeführt. Die Browserumgebung enthält Go und eine fertige Seite. Diese Einheit startet unabhängig und benötigt keine Dateien aus einem anderen Lab. Plane einschließlich Lesen und gewöhnlicher Fehler etwa 55 Minuten ein; dies ist eine Schätzung des Autors.

Einen Gegenstand auswählen und die Liste erhalten

In diesem Schritt erhältst du die funktionierende Sammlung mit einer Karte und ergänzt echte Gegenstandsdetails. Beginne im Tab Workspace. Seine Shell befindet sich in /home/labex/project/lost-found; wenn du später zurückkehrst, stellt das folgende cd diesen Ort wieder her.

Go ist unter /usr/local/go/bin installiert. export fügt dieses Verzeichnis dem Befehlssuchpfad dieser Shell hinzu und erhält den vorhandenen $PATH. Das gilt nur für die aktuelle Terminalsitzung; wiederhole es in einer neuen Shell. Globale Shell-Einstellungen werden nicht geändert.

cd /home/labex/project/lost-found
export PATH=/usr/local/go/bin:$PATH

curl sendet eine Anfrage. -i schließt die Antwortheader ein, und einfache Anführungszeichen halten die URL zusammen. GET fordert das Lesen einer Ressource an. /api/items bezeichnet die Sammlung; 127.0.0.1:8080 ist der Dienst auf derselben Maschine. Verwende zuerst das normale Beispiel:

curl -i 'http://127.0.0.1:8080/api/items'

Erwarte 200 OK, einen Header Content-Type: application/json und ein Array mit LF-101, dem blauen Regenschirm aus der Bibliothek. Der HTTP-Status beschreibt das Ergebnis der Anfrage; der Body enthält Daten. JSON verwendet eckige Klammern für Arrays und geschweifte Klammern für Objekte. Eine 200-Antwort allein belegt keine vollständige Sammlung. Wechsle zu Lost and Found, sieh dir die einzelne Karte an und kehre zu Workspace zurück.

cat zeigt eine Datei an, ohne sie zu verändern. Lies die Quelldatensätze, die Eingabestruktur und deinen kleinen Starthandler:

cat items.json
cat main.go
cat handler.go

Es gibt sechs Datensätze. Item fasst ID, Name, Category und Location zusammen; exportierte Feldnamen mit großem Anfangsbuchstaben können codiert werden. Tags wie `json:"id"` legen kleingeschriebene JSON-Schlüssel fest. main.go lädt die Sammlung, liefert die vorbereitete Seite aus und übergibt den Objekt-Slice an catalogHandler. Erhalte main.go, go.mod, items.json, serve.py und web/ unverändert; bearbeite nur handler.go und gegebenenfalls zusätzliche Go-Dateien oder Tests.

Sage vor der Codeänderung den Unterschied zwischen einer Anfrage nach LF-101 und einer nach LF-103 voraus. Beide Namen lauten Blue umbrella, aber die Orte sind Library desk und Cafe counter. Welches Feld kann genau einen Datensatz auswählen? Frage nun die zweite ID ab:

curl -i 'http://127.0.0.1:8080/api/items/LF-103'

Der Starthandler liefert weiterhin sein Array mit dem ersten Datensatz. Das ist eine empfangene Antwort, aber kein korrektes Detail. Deine Aufgabe ist, das durch die ID ausgewählte Objekt zurückzugeben und gleichzeitig die aktuelle Listenantwort verfügbar zu halten. Löse dies nicht dadurch, für jeden Pfad das erste Objekt zurückzugeben.

Ein Handler ist Go-Code, der für jede Anfrage aufgerufen wird. r *http.Request zeigt auf die eingehende Anfrage. w http.ResponseWriter ist das Ziel: Header setzt Header, und Schreibvorgänge senden den Body. http.Handler beschreibt dieses Verhalten; http.HandlerFunc passt eine Funktion daran an. Die zurückgegebene anonyme Funktion behält die übergebenen items. Du musst keinen HTTP-Parser schreiben.

Sichere vor dem Bearbeiten eine Kopie. cp kopiert die Datei; die Endung .backup vermeidet bewusst .go, weil sonst beim Kompilieren des Pakets doppelte Definitionen entstünden.

cp handler.go handler.go.backup

Öffne die tatsächliche Datei im Terminaleditor. Das auf diesen Befehl beschränkte LC_ALL=C.UTF-8 lässt nano Text einschließlich Akzentzeichen als UTF-8 interpretieren. Es wirkt nur für diesen Editoraufruf, nicht auf globale Shell-Einstellungen:

LC_ALL=C.UTF-8 nano handler.go

In nano bedeutet Ctrl, Control beim Drücken der genannten Taste zu halten. Bewege dich mit den Pfeiltasten. Drücke Ctrl+W, gib catalogHandler ein und drücke Enter, um die Funktion zu finden; Ctrl+C bricht eine unvollständige Suche ab. Bearbeite mit normaler Texteingabe, Backspace und Delete. Ctrl+K schneidet die aktuelle Zeile aus; verwende es nur auf einer Zeile, die du ersetzen möchtest. Die Tastenkürzel am unteren Rand verwenden ^ für Control. Du bearbeitest den vorhandenen Handler und erstellst kein zweites Programm.

Implementiere die folgenden Entscheidungen innerhalb der zurückgegebenen Funktion hinter ihrem vorhandenen Content-Type-Header. Bearbeite jeweils einen Zweig:

Lies zuerst r.URL.Path. Ist er gleich /api/items, behalte die Array-Codierung items[:1] des Starthandlers bei und kehre sofort zurück. So bleibt die normale Liste mit einer Karte während der Arbeit an den Details erhalten.

Erkenne andernfalls das Präfix /api/items/. Ergänze den Standardbibliotheksimport strings bei den vorhandenen Imports. strings.HasPrefix(path, prefix) prüft ein Präfix, und strings.TrimPrefix(path, prefix) entfernt es. Speichere die verbleibende ID in einer lokalen Variablen.

Durchlaufe dann den übergebenen Slice mit for _, item := range items. Vergleiche item.ID mit der angefragten ID, nicht den Namen. Codiere bei einem Treffer dieses item-Objekt und kehre sofort zurück. json.NewEncoder(w).Encode(value) schreibt den ausgewählten Go-Wert als JSON; ein Slice wird zu einem Array, eine einzelne Struktur zu einem Objekt. Die Schleife muss jeden Datensatz prüfen, nicht nur items[0].

Wenn keine ID passte oder der Pfad außerhalb dieser Routen liegt, setze anschließend 404, bevor du einen JSON-Fehler schreibst. w.WriteHeader(http.StatusNotFound) setzt den Status. Ein kleiner JSON-kompatibler Fehlerwert ist map[string]string{"error": "item not found"}; die Zeichenkette error darf nicht leer sein. Codiere diese Map. Schreibe nach einem Fehler keine weitere Liste.

Der erste Schreibvorgang einer Antwort legt normalerweise 200 fest; setze einen anderen Status deshalb vor dem Codieren. Encode gibt einen error zurück: nil bedeutet keinen Fehler. In diesem Beispiel sind alle Werte JSON-kompatible Zeichenketten. Ein Client mit getrennter Verbindung kann trotzdem einen Schreibfehler verursachen; eine zweite Antwort nach Beginn des Bodys kann die Verbindung nicht reparieren. Frühe Rückgaben verhindern außerdem, dass zwei Antworten aneinandergehängt werden.

Speichere mit Ctrl+O, bestätige den vorhandenen Dateinamen handler.go mit Enter und beende mit Ctrl+X. Prüfe deine gespeicherte Datei:

cat handler.go

Hast du die falsche Datei geöffnet, beende ohne Speichern: Ctrl+X, dann N, falls nach dem Speichern gefragt wird. Prüfe deinen Ort mit pwd, wiederhole das erste cd und öffne den richtigen Dateinamen erneut. Zeigt die Speicherabfrage einen unbeabsichtigten Namen, bricht Ctrl+C sie ab. Überschreibe keine Quelldaten, um einen Bearbeitungsfehler auszugleichen.

gofmt -w formatiert die angegebene Go-Datei direkt. Das vorbereitete serve.py baut deinen Code, bevor es nur den aufgezeichneten Server dieses Arbeitsbereichs ersetzt. Es liefert keine Handler-Implementierung. Bei einem Buildfehler bleibt der bisherige funktionierende Prozess erhalten; eine alte Browserantwort beweist deshalb nicht, dass der neue Code kompiliert wurde.

gofmt -w handler.go
python3 serve.py

Erwarte Lost and Found ready on port 8080. Schlägt Formatierung oder Kompilierung fehl, lies Dateiname und Zeilennummer. Öffne nano handler.go erneut, finde den benachbarten Ausdruck mit Ctrl+W, korrigiere nur diesen Bereich, speichere, beende und wiederhole beide Befehle. Erhalte bereits funktionierende Logik; überschreibe späteren Fortschritt nicht mit der anfänglichen Sicherung. Bei No such file wiederhole das erste cd. Dienstausgaben stehen in .work/server.log, lesbar mit cat .work/server.log; ein Tippfehler ist kein Grund für einen VM-Reset.

curl -i 'http://127.0.0.1:8080/api/items/LF-103'

Erwarte ein einzelnes Objekt mit id gleich LF-103 und location gleich Cafe counter, kein Array oder den Regenschirm der Bibliothek. Prüfe, ob die normale Liste weiterhin ihr Beispielarray liefert:

curl -i 'http://127.0.0.1:8080/api/items'

Du hast eine Auswahl ergänzt und dabei das Sammlungsformat der funktionierenden Seite erhalten. Der nächste Schritt erweitert diese Liste bewusst auf alle Datensätze.

Die vollständige Sammlung zurückgeben

In diesem Schritt machst du aus der Liste mit einem Datensatz einen vollständigen schreibgeschützten Katalog, ohne deine geschriebenen Details zu verlieren. Beginne in Workspace unter /home/labex/project/lost-found.

Lies vor dem Bearbeiten den Slice-Ausdruck im Sammlungszweig. items[:1] wählt ein Präfix bis vor Index eins aus und verbirgt so die übrigen fünf Datensätze. Sage die Antwort voraus, wenn der Encoder items selbst erhält. Anders als ein fest codiertes Array mit sechs Elementen funktioniert dieses Argument auch mit einem anderen übergebenen Slice.

LC_ALL=C.UTF-8 nano handler.go

Suche mit Ctrl+W und Enter nach items[:1]. Ändere nur den codierten Wert der Sammlung auf den vollständigen Eingabe-Slice. Erhalte JSON-Header, Detailschleife, Fehler für fehlende IDs und frühe Rückgaben. Speichere mit Ctrl+O und Enter unter demselben Dateinamen und beende mit Ctrl+X.

Eine leere Sammlung muss als [] codiert werden, niemals als null. Ein leerer Slice, der nicht nil ist, wird als [] codiert; ein nil-Slice würde als null codiert. Die unabhängige leere Eingabe dieser Einheit ist []Item{}. Ersetze ein leeres Ergebnis nicht durch einen Fehler für einen fehlenden Gegenstand: Liste und Detail haben unterschiedliche Bedeutungen. Array- und Objektschlüsselreihenfolge sind frei, aber die zu jeder ID gehörenden Werte für Name, Kategorie und Ort müssen zusammenbleiben.

gofmt -w handler.go
python3 serve.py

Rufe die Sammlung und anschließend beide Regenschirme über ihre unterschiedlichen IDs ab:

curl -i 'http://127.0.0.1:8080/api/items'
curl -i 'http://127.0.0.1:8080/api/items/LF-101'
curl -i 'http://127.0.0.1:8080/api/items/LF-103'

Erwarte alle sechs vollständigen Datensätze im Array. LF-101 gehört weiterhin zu Library desk und LF-103 zu Cafe counter, obwohl beide Namen Blue umbrella lauten. Hat sich ein Detail verschoben, prüfe den ID-Vergleich und den Schleifenwert, den du codierst.

Die nächste Anfrage verwendet absichtlich eine fehlende ID. Erwarte HTTP 404 und ein JSON-Objekt mit einer nicht leeren error-Zeichenkette statt einer leeren Liste. curl beendet sich normalerweise auch bei einer empfangenen HTTP-Fehlerantwort erfolgreich: Eine Antwort zu empfangen ist etwas anderes, als keine Verbindung herstellen zu können.

curl -i 'http://127.0.0.1:8080/api/items/no-such-id'

Dieser Handler filtert noch nicht und erzwingt keine HTTP-Methode; das nächste geführte Lab führt diese Verhaltensweisen ein. Betrachte weder eine normale Startseite noch einen beliebigen 200-Status als Beleg für einen funktionierenden Detailendpunkt. Du hast nun Liste und Details gemeinsam erhalten und dabei den Umfang der Sammlung geändert.

Einen Test schreiben, der Identitäten unterscheidet

In diesem Schritt entwirfst du einen eigenen gezielten Handler-Test ohne einen zweiten laufenden Server und verwendest dann die echte Seite. Beginne in Workspace im selben Verzeichnis.

Entscheide zuerst, was ein Test erkennen muss: Ein Handler könnte den ersten passenden Namen statt der angefragten ID zurückgeben. Wähle außerhalb der Webseitensammlung zwei eigene kleine Datensätze mit gleichem Namen, aber verschiedenen IDs und Orten. Frage die zweite ID an. Notiere, welcher Status, welche ID und welcher Ort zurückgegeben werden sollen. Diese unabhängige Testeingabe lässt eine feste Antwort des laufenden Diensts scheitern und macht den Test über die sechs vorbereiteten Datensätze hinaus nützlich.

Go findet Tests in Dateien mit der Endung _test.go. Eine Testfunktion beginnt mit Test und erhält t *testing.T. Erstelle deine Testdatei mit nano:

LC_ALL=C.UTF-8 nano handler_test.go

Beginne mit package main. Importiere encoding/json, net/http/httptest und testing. Ergänze eine Funktion wie func TestSecondIdentity(t *testing.T) { ... } und ersetze die Punkte durch deine Testanweisungen. Der Funktionsrumpf hat vier Teile:

Erstelle zuerst deine Testeingabe mit zwei Datensätzen als []Item und den exportierten Feldnamen aus main.go. Übergib sie an catalogHandler, statt items.json zu laden oder zu ändern.

Erstelle als Nächstes eine Anfrage nach dem Detail des zweiten Datensatzes. httptest.NewRequest("GET", path, nil) erstellt eine Anfrage; das letzte Argument bedeutet keinen Anfragebody. httptest.NewRecorder() liefert einen Antwort-Recorder. Der Aufruf von ServeHTTP(recorder, request) an deinem Handler führt den echten Handler aus und erfasst Header, Status und Body.

Deklariere dann var got Item. Decodiere mit json.Unmarshal(recorder.Body.Bytes(), &got). &got ist die Adresse, an der die Felder gespeichert werden. Prüfe zuerst den zurückgegebenen error: Ungültiges JSON darf nicht mit einer gültigen leeren Struktur verwechselt werden. Beispielsweise beendet if err != nil { t.Fatal(err) } den Test mit einer lesbaren Fehlermeldung.

Prüfe anschließend erwarteten Status und Identität. recorder.Code enthält den Status, got.ID und got.Location die decodierten Felder. Vergleiche sie mit dem zweiten Testdatensatz. Lass den Test mit if und t.Fatalf scheitern und gib die tatsächlichen Werte aus. %d zeigt eine Zahl und %+v Strukturfelder. Prüfe nicht bloß, ob irgendwelches JSON vorhanden ist.

Verwende deine gewählten IDs und Orte konsistent in Anfrage und Prüfungen. Halte Testdaten, Ausführung und Prüfungen in derselben Testfunktion zusammen, damit die Überlegung nachvollziehbar bleibt. Speichere mit Ctrl+O und Enter und beende mit Ctrl+X. Die Umgebung liefert dir keinen bereits erfolgreichen Test.

go test -v . führt die Tests dieses Pakets aus: -v zeigt Testnamen, und . wählt das aktuelle Paket.

gofmt -w handler_test.go
go test -v .

Erwarte deinen Testnamen, gefolgt von PASS. Bei einem Fehlschlag sollte der echte Status oder Gegenstand ausgegeben werden. Entscheide dann, ob die erwartete Beziehung deiner Testdaten oder der Handler falsch ist; korrigiere nur diesen Teil mit nano und führe den Test erneut aus. Ein Compilerfehler nennt eine zu prüfende Datei und Zeile. Baue nach jeder Handlerkorrektur mit python3 serve.py neu, damit auch der Browser den aktuellen Code verwendet. Behalte mindestens einen erfolgreichen Go-Handler-Test in diesem Arbeitsbereich; die abschließende Prüfung führt deine Tests isoliert aus und prüft die API mit anderen übergebenen Eingabedatensätzen.

Wechsle zu Lost and Found und lade neu. Erwarte 6 items found. Öffne View item bei LF-103 und lies Referenz und Abholort; wähle anschließend Back to collection. Die beiden Regenschirme müssen unterscheidbar bleiben. Diese Einheit ermöglicht das Durchsuchen der Sammlung und der Details; das nächste geführte Lab ergänzt Suchbedienelemente zusammen mit ihrer API-Filterung.

Die echte Katalogseite mit allen Quelldatensätzen

Achte auf sechs Karten und ihre individuellen Referenzen. Die Seite verwendet deine laufende API und enthält keine Ersatzsammlung. Kehre zu Workspace zurück, falls eine Korrektur nötig ist. Du hast nun einen nutzbaren Katalog und einen Test, dessen Eingaben und Prüfungen du selbst gewählt hast, um einen echten Identitätsfehler aufzudecken.

Zusammenfassung

Du hast Go-Datensätze als JSON codiert, Listen- und Detailantworten anhand des Pfads ausgewählt, gleiche Namen mit ihren jeweiligen IDs verknüpft und für einen fehlenden Gegenstand einen echten Fehlerstatus zurückgegeben. Außerdem hast du einen isolierten Test mit httptest geschrieben und die laufende Webseite verwendet. Suche und strengere Fehlerbehandlung sind die eigenständige Aufgabe des nächsten geführten Labs.