Go HTTP-Client RoundTripper - Interceptor und Filter

Go HTTP-Client RoundTripper - Interceptor und Filter

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.

Roundtripper

http.RoundTripper - Manchmal verworren

HTTP-Request für PUT, DELETE usw.

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)

HTTP-Header an den Server senden

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.

Interceptor bzw. Filter nutzen

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.

RoundTripper UML

http.RoundTripper Implementierung in Golang

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

Kennen Sie schon das Buch zum Thema?

Der praktische Soforteinstieg für Developer und Softwarearchitekten, die direkt mit Go produktiv werden wollen.

  • Von den Sprachgrundlagen bis zur Qualitätssicherung
  • Architekturstil verstehen und direkt anwenden
  • Idiomatic Go, gRPC, Go Cloud Development Kit
  • Cloud-native Anwendungen erstellen
Microservices mit Go Buch

zur Buchseite beim Rheinwerk Verlag Rheinwerk Computing, ISBN 978-3-8362-7559-0 (als PDF, EPUB, MOBI und Papier)

Kontakt

Source Fellows GmbH

Source Fellows GmbH Logo

Lerchenstraße 31

72762 Reutlingen

Telefon: (0049) 07121 6969 802

E-Mail: info@source-fellows.com