Entwicklung von Web-Servern mit Golang — von einfach bis komplex

Entwicklung von Web-Servern mit Golang — von einfach bis komplex

Vor fünf Jahren begann ich Gophish zu entwickeln, was mir die Möglichkeit gab, Golang zu lernen. Ich erkannte, dass Go eine mächtige Sprache ist, deren Fähigkeiten durch zahlreiche Bibliotheken ergänzt werden. Go ist vielseitig: Insbesondere können mit ihm problemlos Serveranwendungen entwickelt werden.

Dieser Artikel ist der Erstellung eines Servers mit Go gewidmet. Wir beginnen mit einfachen Dingen wie „Hello World!“, und enden mit einer Anwendung, die folgende Funktionen bietet:

— Verwendung von Let’s Encrypt für HTTPS.
— Funktionsweise als API-Router.
— Umgang mit Middleware.
— Verarbeitung statischer Dateien.
— Sauberes Herunterfahren.

Skillbox empfiehlt: Praktischer Kurs „Python-Entwickler von Grund auf“.

Wir erinnern daran: alle Leser von „Habr“ erhalten einen Rabatt von 10.000 Rubel bei der Anmeldung zu einem beliebigen Kurs von Skillbox mit dem Aktionscode „Habr“.

Hallo, Welt!

Einen Web-Server in Go kann man sehr schnell erstellen. Hier ist ein Beispiel für die Verwendung eines Handlers, der das oben versprochene „Hallo, Welt!“ zurückgibt.

package main
 
import (
"fmt"
"net/http"
)
 
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
})
http.ListenAndServe(":80", nil)
}

Nachdem Sie dies getan haben, starten Sie die Anwendung und öffnen die Seite localhost, dann sehen Sie sofort den Text „Hallo, Welt!“ (vorausgesetzt, alles funktioniert richtig).

Als nächstes werden wir den Handler mehrfach verwenden, aber zuerst lassen Sie uns verstehen, wie alles aufgebaut ist.

net/http

Im Beispiel wurde das Paket net/http, das Hauptwerkzeug in Go zur Entwicklung sowohl von Servern als auch von HTTP-Clients. Um den Code zu verstehen, lassen Sie uns die Bedeutung von drei wichtigen Elementen klären: http.Handler, http.ServeMux und http.Server.

HTTP-Handler

Wenn wir eine Anfrage erhalten, analysiert der Handler sie und formt eine Antwort. Die Handler in Go sind folgendermaßen implementiert:

type Handler interface {
        ServeHTTP(ResponseWriter, *Request)
}

Im ersten Beispiel wird die Hilfsfunktion http.HandleFunc verwendet. Sie wickelt eine andere Funktion ein, die wiederum http.ResponseWriter und http.Request in ServeHTTP entgegennimmt.

Anders gesagt, die Handler in Golang werden durch ein einheitliches Interface dargestellt, was den Programmierern viele Möglichkeiten bietet. Zum Beispiel wird Middleware durch einen Handler implementiert, bei dem ServeHTTP zuerst etwas macht und dann die Methode ServeHTTP eines anderen Handlers aufruft.

Wie bereits erwähnt, erstellen Handler einfach Antworten auf Anfragen. Aber welchen Handler sollte man zu einem bestimmten Zeitpunkt verwenden?

Routing von Anfragen

Um die richtige Wahl zu treffen, nutzen Sie einen HTTP-Multiplexer. In verschiedenen Bibliotheken wird er als muxer oder Router bezeichnet, aber das ist alles dasselbe. Die Funktion des Multiplexers besteht darin, den Pfad der Anfrage zu analysieren und den entsprechenden Handler auszuwählen.

Sollte jedoch komplexes Routing benötigt werden, ist es besser, auf externe Bibliotheken zurückzugreifen. Einige der fortschrittlichsten sind gorilla/mux и go-chi/chi, diese Bibliotheken ermöglichen eine einfache Implementierung der Middleware. Mit ihnen können Sie Wildcard-Routing einrichten und eine Reihe anderer Aufgaben durchführen. Ihr Vorteil ist die Kompatibilität mit den standardmäßigen HTTP-Handlern. Das Ergebnis ist, dass Sie einfachen Code schreiben können, der zukünftig modifiziert werden kann.

Die Arbeit mit komplexen Frameworks erfordert in der Regel nicht standardisierte Lösungen, was die Nutzung der Standard-Handler erheblich erschwert. Für die meisten Anwendungen reicht eine Kombination aus der Standardbibliothek und einem einfachen Router aus.

Anfragebearbeitung

Zusätzlich benötigen wir eine Komponente, die eingehende Verbindungen »überwacht« und alle Anfragen an den richtigen Handler weiterleitet. Diese Aufgabe kann problemlos vom http.Server übernommen werden.

Im Folgenden sehen Sie, dass der Server für alle Aufgaben verantwortlich ist, die mit der Verarbeitung von Verbindungen zu tun haben. Dazu gehört beispielsweise die Arbeit mit dem TLS-Protokoll. Der Aufruf von http.ListenAndServer nutzt den Standard-HTTP-Server.

Nun schauen wir uns komplexere Beispiele an.

Hinzufügen von Let’s Encrypt

Standardmäßig läuft unsere Anwendung über das HTTP-Protokoll, jedoch wird empfohlen, das HTTPS-Protokoll zu verwenden. In Go ist das problemlos möglich. Wenn Sie ein Zertifikat und einen privaten Schlüssel erhalten haben, genügt es, ListenAndServeTLS mit den richtigen Zertifikat- und Schlüsseldateien zu konfigurieren.

http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil)

Man kann immer besser werden.

Let’s Encrypt bietet kostenlose Zertifikate mit automatischer Erneuerungsmöglichkeit an. Um den Service nutzen zu können, benötigen Sie ein Paket autocert.

Die einfachste Möglichkeit, es einzurichten, besteht darin, die Methode autocert.NewListener in Kombination mit http.Serve zu verwenden. Diese Methode ermöglicht es, TLS-Zertifikate zu erhalten und zu erneuern, während der HTTP-Server die Anfragen verarbeitet:

http.Serve(autocert.NewListener("example.com"), nil)

Wenn wir im Browser öffnen example.com, erhalten wir eine HTTPS-Antwort "Hello, world!".

Für eine genauere Konfiguration sollten Sie den Manager autocert.Manager verwenden. Dann erstellen wir unsere eigene Instanz von http.Server (bis jetzt haben wir den Standardserver verwendet) und fügen den Manager in die TLSConfig des Servers ein:

m := &autocert.Manager{
Cache:      autocert.DirCache("golang-autocert"),
Prompt:     autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist("example.org", "www.example.org"),
}
server := &http.Server{
    Addr:      ":443",
    TLSConfig: m.TLSConfig(),
}
server.ListenAndServeTLS("", "")

Dies ist eine einfache Möglichkeit, vollständige Unterstützung für HTTPS mit automatischer Zertifikatserneuerung umzusetzen.

Hinzufügen von benutzerdefinierten Routen

Der standardmäßig integrierte Router ist einfach und gut, reicht jedoch oft nicht aus. In den meisten Anwendungen werden komplexere Routing-Optionen benötigt, einschließlich verschachtelter und Wildcard-Routen sowie Verfahren zur Einrichtung von Vorlagen und Pfadparametern.

In diesem Fall sollten Pakete verwendet werden gorilla/mux и go-chi/chi. Mit dem Letzten werden wir arbeiten lernen — ein Beispiel finden Sie weiter unten.

Gegeben sei die Datei api/v1/api.go, die die Routen für unser API enthält:

/ HelloResponse is the JSON representation for a customized message
type HelloResponse struct {
Message string `json:"message"`
}
 
// HelloName returns a personalized JSON message
func HelloName(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
response := HelloResponse{
Message: fmt.Sprintf("Hello %s!", name),
}
jsonResponse(w, response, http.StatusOK)
}
 
// NewRouter returns an HTTP handler that implements the routes for the API
func NewRouter() http.Handler {
r := chi.NewRouter()
r.Get("/{name}", HelloName)
return r
}

Wir fügen den Routen im Hauptdatei den Präfix api/vq hinzu.

Dann können wir dies in unserem Hauptanwendungsrouter unter dem Prefix api/v1/ einbinden:

// NewRouter returns a new HTTP handler that implements the main server routes
func NewRouter() http.Handler {
router := chi.NewRouter()
    router.Mount("/api/v1/", v1.NewRouter())
    return router
}
http.Serve(autocert.NewListener("example.com"), NewRouter())

Die einfache Handhabung komplexer Routen in Go ermöglicht eine vereinfachte Strukturierung bei der Verwaltung großer, komplexer Anwendungen.

Zusammenarbeit mit Middleware

Bei der Verarbeitung durch Middleware wird ein HTTP-Handler in einen anderen eingekapselt, was es ermöglicht, schnell Authentifizierung, Komprimierung, Protokollierung und einige andere Funktionen durchzuführen.

Als Beispiel betrachten wir das Interface http.Handler, mit dem wir einen Handler für die Benutzerauthentifizierung des Dienstes erstellen.

func RequireAuthentication(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if !isAuthenticated(r) {
            http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
            return
        }
        // Vorausgesetzt, die Authentifizierung war erfolgreich, führen Sie den ursprünglichen Handler aus
        next.ServeHTTP(w, r)
    })
}

Es gibt Drittanbieter-Router wie chi, die die Funktionalität der Middleware erweitern.

Arbeiten mit statischen Dateien

Die Standardbibliothek von Go umfasst Funktionen zur Handhabung von statischen Inhalten, einschließlich Bildern sowie JavaScript- und CSS-Dateien. Der Zugriff erfolgt über die Funktion http.FileServer, die einen Handler zurückgibt, der Dateien aus einem bestimmten Verzeichnis bereitstellt.

func NewRouter() http.Handler {
    router := chi.NewRouter()
    r.Get("/{name}", HelloName)
 
// Einrichtung der Bereitstellung statischer Dateien
staticPath, _ := filepath.Abs("../../static/")
fs := http.FileServer(http.Dir(staticPath))
    router.Handle("/*", fs)
    
    return r

Es ist wichtig zu beachten, dass http.Dir den Inhalt eines Verzeichnisses anzeigt, wenn keine Hauptdatei index.html vorhanden ist. In diesem Fall sollte das Paket verwendet werden, um eine Kompromittierung des Verzeichnisses zu verhindern. unindexed.

Korrekte Beendigung der Arbeit

In Go gibt es eine Funktion für das ordnungsgemäße Herunterfahren eines HTTP-Servers. Dies kann mit der Methode Shutdown() erfolgen. Der Server wird in einer Goroutine gestartet und anschließend wird auf ein Signal zum Abbruch gewartet. Sobald das Signal empfangen wird, schaltet sich der Server ab, allerdings nicht sofort, sondern nach ein paar Sekunden.

handler := server.NewRouter()
srv := &http.Server{
    Handler: handler,
}

go func() {
srv.Serve(autocert.NewListener(domains...))
}()

// Warten auf ein Interrupt-Signal
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c

// Versuch eines sanften Herunterfahrens
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(ctx)

Zusammenfassend

Go ist eine leistungsstarke Programmiersprache mit einer nahezu universellen Standardbibliothek. Die standardmäßigen Funktionen sind bereits sehr umfangreich und können durch Schnittstellen erweitert werden, was die Entwicklung wirklich zuverlässiger HTTP-Server ermöglicht.

Skillbox empfiehlt:

Quelle: habr.com

Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Servern 🔥 Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Servern | ProHoster