Die Implementierung eines HTTP-Clients in Go ist durch das
http
-Package der Standardbibliothek extrem
einfach. Für die beiden HTTP-Methoden GET und POST bietet das Package z. B.
direkt entsprechende Funktionen an. Diese werden auch gerne als
convenience-Funktionen bezeichnetet:
func Get(url string) (resp *Response, err error)
func Post(url, contentType string, body io.Reader) (resp *Response, err error)
Nutzt man diese Funktionen sieht eine komplette Go Client-Anwendung zum Abruf eines HTTP-Service z. B. entsprechend so aus:
package main
import (
"log"
"net/http"
)
func main() {
response, err := http.Get("http://localhost:8080")
if err != nil {
log.Fatal(err)
}
log.Printf("Server retunred status %d", response.StatusCode)
}
Mehr ist nicht nötig um per HTTP auf einen Server zuzugreifen, der seine
Ressourcen über GET
oder POST
anbietet.
Sollen andere
HTTP-Methoden, wie
z. B. PUT
, DELETE
oder HEAD
verwendet werden, muss ein entsprechender
http.Request
erstellt und konfiguriert werden. Das ist etwas umfangreichen,
aber auch kein Beinbruch.
Das folgende Beispiel zeigt beispielsweise wie ein HTTP-HEAD
Request mit der
Funktion
NewRequestWithContext
erzeugt und mittels dem DefaultClient
ausgeführt werden kann.
Die Funktion zum Erzeugen eines Requests erfordert 4 Parameter:
einen context.Context
die entsprechende HTTP-Methode (wie z. B. GET
, PUT
, POST
, DELETE
).
Hierzu stehen Konstanten, wie z. B. http.MethodHead
, in der
Standardbibliothek zur Verfügung.
die aufzurufende URL
den zu übertragenden HTTP-Body (im Beispiel wird kein Body benötigt und es wir
nil
übergeben)
Nach dem Ausführen des Requests über den DefaultClient
unterscheidet sich der
Code dann nicht mehr. Die Auswertung der Serverantwort ist unabhängig davon, ob
der Request über die convenience-Funktion oder über die “ausführliche”
Variante erstellt wurde.
ctx := context.Background()
//Request erstellen
request, err := http.NewRequestWithContext(ctx,
http.MethodHead, "http://localhost:8080", nil)
if err != nil {
log.Fatal(err)
}
//Request ausführen
response, err := http.DefaultClient.Do(request)
if err != nil {
log.Fatal(err)
}
//Antwort auswerten
log.Printf("Server retunred status %d", response.StatusCode)
In den meisten Anwendungsszenarien müssen meist neben den reinen Nutzdaten noch zusätzliche Informationen, wie z. B. eine Authorisierungsinformationen, an den Server übermittelt werden. Das geschieht dann innerhalb der HTTP-Header.
Diese Header-Werte können in Go vor dem Senden des Requests an den Server mit einem einfachen Call gesetzt werden. Das funktioniert bei den convenience-Varianten nicht, da die API das Request-Objekt nicht nach außen gibt. Wurde der Request über die “ausführliche” API erstellt, sieht ein Call so aus:
request.Header.Set("Accept", "application/json")
request.Header.Set("Authorization", "Bearer ...")
Müssen für jeden Call diese Informationen mitgesendet werden, kann das unter Umständen recht umfangreich werden, je nachdem wieviele Informationen übertragen werden sollen. Neben den Anmeldedaten sind oftmals Tracing-Informationen wie Span- oder TraceIds sinnvoll. Mit der Trace-Context Spezifikation werden sogar mehrere HTTP-Header erwartet.
Der Go HTTP-Client bietet die Möglichkeit sogenannte http.RoundTripper
zu
implementieren und als Transport
-Objekt am HTTP-Client zu konfigurieren. Auf
diese Weise kann, wenn man von Design Patterns sprechen möchte, ein Filter,
Interceptor oder Chain of responsibility umgesetzt werden. Jeder Aufruf wird
durch eine Kette von http.RoundTripper
Objekten geschickt, bis er
schlußendlich zum Server übertragen wird.
In folgendem Beispiel setzt der VersionRoundTripper beispielsweise ein spezielles HTTP-Header Feld mit er Software-Version des Clients.
type VersionRoundTripper struct {
wrapped http.RoundTripper
clientVersion string
}
func (vrt VersionRoundTripper) RoundTrip(req *http.Request) (res *http.Response, e error) {
req.Header.Add(ClientVersionHeaderName, vrt.clientVersion)
return vrt.wrapped.RoundTrip(req)
}
Wird anschließend der genutzte HTTP-Client entsprechend konfiguriert, erhält jeder Aufruf, unabhängig von der Aufrufweise, automatisch die gewünschte Informationen in jedem Call.
func createClient(clientVersion string) *http.Client {
return &http.Client{
Transport: VersionRoundTripper{
wrapped: &http.Transport{},
clientVersion: clientVersion,
},
}
}
Für das Beispiel oben kann entweder der DefaultClient
direkt angepasst werden,
oder ein separater Client für die Aufrufe genutzt werden. Easy!
Natürlich lassen sich http.RoundTripper
für alles mögliche implementieren.
Wichtig ist zu sehen, dass man nicht für jeden Aufruf die HTTP-Header erneut
setzen muss.
Manchen “Java-Menschen” mag es komisch vorkommen den
DefaultClient
anzupassen, und so für ALLE Bestandteile der Anwendung zu setzen. Aber ist es nicht das Ziel des Ganzen?
Das Setzen des DefaultClients sieht dann wie folgt aus:
http.DefaultClient = &http.Client{
Transport: VersionRoundTripper{
clientVersion: "1.0",
wrapped: &http.Transport{},
},
}
response, err = http.Get("http://localhost:8080")
if err != nil {
log.Fatal(err)
}
log.Printf("Server retunred status %d", response.StatusCode)
Auch wenn der Einsatz der convenience-Funktionen verführerisch ist, sollte
möglichst eine API mit einem context.Context
Parameter verwendet werden. Nur
so können Abbruchbedingungen oder weitere Werte “durch die Anwendung” propagiert
werden.
Mit http.RoundTripper
kann auf einfache Weise sich wiederholender Code gespart
werden. Oftmals kann zusätzlich auf eine vorhandene Bibliothek, die intern auf
HTTP-Kommunikation setzt, eingesetzt werden, ohne auf die eigenen Header-Werte
zu verzichten. Meist kann ein http.Client
bei Aufrufen oder der
Initialisierung mitgegeben werden und so Standardverhalten “erzwungen” werden.
Viel Spaß damit! Happy coding!
30.04.2024
Der Author auf LinkedIn: Kristian Köhler und Mastodon: @kkoehler@mastodontech.de
Der praktische Soforteinstieg für Developer und Softwarearchitekten, die direkt mit Go produktiv werden wollen.
zur Buchseite beim Rheinwerk Verlag Rheinwerk Computing, ISBN 978-3-8362-7559-0 (als PDF, EPUB, MOBI und Papier)
Source Fellows GmbH
Lerchenstraße 31
72762 Reutlingen
Telefon: (0049) 07121 6969 802
E-Mail: info@source-fellows.com