Entwicklung von Webservern in Golang — von einfach bis komplex

Entwicklung von Webservern in Golang — von einfach bis komplex

Vor fĂŒnf Jahren habe ich begonnen Gophish zu entwickeln, was mir die Möglichkeit gab, Golang zu lernen. Ich erkannte, dass Go eine leistungsstarke Sprache ist, deren Möglichkeiten durch eine Vielzahl von Bibliotheken erweitert werden. Go ist vielseitig: insbesondere kann man damit problemlos serverseitige Anwendungen entwickeln.

Dieser Artikel ist dem Schreiben eines Servers in Go gewidmet. Lassen Sie uns mit einfachen Dingen wie „Hello World!“ beginnen und enden mit einer Anwendung, die folgende Funktionen bietet:

— Verwendung von Let’s Encrypt fĂŒr HTTPS.
— Funktion als API-Router.
— Arbeiten mit Middleware.
— Verarbeitung statischer Dateien.
— Ordentliches 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!

Es ist sehr einfach, einen Webserver in Go zu erstellen. Hier ist ein Beispiel, das einen Handler verwendet, 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, "Hallo Welt!")
})
http.ListenAndServe(":80", nil)
}

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

Im Folgenden werden wir den Handler mehrfach verwenden, aber lassen Sie uns zunÀchst verstehen, wie alles funktioniert.

net/http

Im Beispiel wurde das Paket net/http, welches das Hauptwerkzeug in Go fĂŒr die Entwicklung sowohl von Servern als auch von HTTP-Clients ist, verwendet. 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 die Antwort. Handler in Go sind wie folgt implementiert:

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

Im ersten Beispiel wird die Hilfsfunktion http.HandleFunc verwendet. Sie umschließt eine andere Funktion, die wiederum http.ResponseWriter und http.Request in ServeHTTP akzeptiert.

Anders ausgedrĂŒckt, sind die Handler in Golang durch ein einheitliches Interface dargestellt, was den Programmierern viele Möglichkeiten bietet. So wird beispielsweise Middleware durch einen Handler realisiert, bei dem ServeHTTP zunĂ€chst etwas macht und dann die Methode ServeHTTP eines anderen Handlers aufruft.

Wie bereits erwÀhnt, formt der Handler einfach die Antworten auf die Anfragen. Aber welchen Handler sollten wir zu einem bestimmten Zeitpunkt verwenden?

Routing von Anfragen

Um eine fundierte Entscheidung zu treffen, nutzen Sie den HTTP-Multiplexer. In einigen 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.

Wenn jedoch UnterstĂŒtzung fĂŒr komplexe Routen erforderlich ist, ist es besser, auf externe Bibliotheken zurĂŒckzugreifen. Einige der fortschrittlichsten sind gorilla/mux und go-chi/chi, diese Bibliotheken ermöglichen eine problemlose Implementierung von Middleware. Mit ihrer Hilfe kann Wildcard-Routing konfiguriert und eine Reihe anderer Aufgaben erledigt werden. Ihr Vorteil ist die KompatibilitĂ€t mit den Standard-HTTP-Handlern. So können Sie einfachen Code schreiben, den Sie in Zukunft modifizieren können.

Die Arbeit mit komplexen Frameworks erfordert in der Regel nicht ganz standardisierte Lösungen, was die Verwendung von Standard-Handlern erheblich kompliziert. FĂŒr die meisten Anwendungen reicht die Kombination aus der Standardbibliothek und einem einfachen Router aus.

Anfragenverarbeitung

Zudem benötigen wir eine Komponente, die eingehende Verbindungen "ĂŒberwacht" und alle Anfragen an den richtigen Handler weiterleitet. Diese Aufgabe kann problemlos von http.Server ĂŒbernommen werden.

Unten 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. FĂŒr den Aufruf von http.ListenAndServer wird der Standard-HTTP-Server verwendet.

Jetzt schauen wir uns komplexere Beispiele an.

HinzufĂŒgen von Let’s Encrypt

StandardmĂ€ĂŸig lĂ€uft unsere Anwendung ĂŒber das HTTP-Protokoll, es wird jedoch empfohlen, das HTTPS-Protokoll zu verwenden. In Go kann dies problemlos umgesetzt werden. Wenn Sie ein Zertifikat und einen privaten SchlĂŒssel erhalten haben, reicht es aus, ListenAndServeTLS mit den richtigen Zertifikat- und SchlĂŒsseldateien zu verwenden.

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

Es gibt immer Raum fĂŒr Verbesserungen.

Let’s Encrypt bietet kostenlose Zertifikate mit automatischer VerlĂ€ngerung. Um den Dienst zu nutzen, benötigen Sie das Paket autocert.

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

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

Wenn wir es im Browser öffnen example.com, dann erhalten wir eine HTTPS-Antwort „Hallo, Welt!“.

Wenn eine genauere Konfiguration erforderlich ist, sollten Sie den Manager autocert.Manager verwenden. Dann erstellen wir unsere eigene Instanz von http.Server (bis jetzt haben wir die Standardinstanz verwendet) und fĂŒgen den Manager zum Server TLSConfig hinzu:

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 zu implementieren.

HinzufĂŒgen von benutzerdefinierten Routen

Der Standard-Router, der in die Standardbibliothek integriert ist, ist zwar gut, aber er ist sehr einfach. In den meisten Anwendungen wird eine komplexere Routing-Lösung benötigt, einschließlich verschachtelter und Wildcard-Routen oder einer Methode zur Festlegung von Mustern und Routenparametern.

In diesem Fall sollten Sie die Pakete verwenden gorilla/mux und go-chi/chi. Mit dem Letzten werden wir arbeiten lernen — ein Beispiel ist unten dargestellt.

Gegeben ist eine Datei api/v1/api.go, die die Routen fĂŒr unsere 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 setzen den Routen-Prefix api/vq in der Hauptdatei fest.

Dann können wir dies in unseren Haupt-Router unter dem Prefix api/v1/ in unserer Hauptanwendung einfĂŒgen:

// 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 es, die Strukturierung der Wartung großer komplexer Anwendungen zu vereinfachen.

Arbeiten mit Middleware

Im Fall der Middleware wird ein HTTP-Handler in einen anderen gewickelt, was schnelle Authentifizierung, Kompression, Protokollierung und einige andere Funktionen ermöglicht.

Als Beispiel betrachten wir das Interface http.Handler, mit dem wir einen Handler fĂŒr die Benutzer-Authentifizierung des Dienstes schreiben werden.

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
        }
        // Angenommen, die Authentifizierung war erfolgreich, fĂŒhre den ursprĂŒnglichen Handler aus
        next.ServeHTTP(w, r)
    })
}

Es gibt externe Router wie chi, die die FunktionalitÀt der Middleware erweitern.

Arbeiten mit statischen Dateien

Die Standardbibliothek von Go enthĂ€lt Funktionen zur Arbeit mit statischen Inhalten, einschließlich Bildern sowie JavaScript- und CSS-Dateien. Der Zugriff erfolgt ĂŒber die Funktion http.FileServer. Diese gibt einen Handler zurĂŒck, der Dateien aus einem bestimmten Verzeichnis ausliefert.

func NewRouter() http.Handler {
    router := chi.NewRouter()
    r.Get("/{name}", HelloName)
 
// Konfiguration 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 nur anzeigt, wenn darin keine index.html-Datei vorhanden ist. In diesem Fall sollte das Paket verwendet werden, um eine Kompromittierung des Verzeichnisses zu vermeiden. nicht indiziert.

Sanftes Herunterfahren

In Go gibt es auch eine Funktion zum sanften Herunterfahren des HTTP-Servers. Dies kann mit der Methode Shutdown() erfolgen. Der Server wird in einer Goroutine gestartet, und dann wird ein Kanal abgehört, um ein Unterbrechungssignal zu empfangen. Sobald das Signal empfangen wird, wird der Server abgeschaltet, jedoch nicht sofort, sondern nach einigen Sekunden.

handler := server.NewRouter()
srv := &http.Server{
    Handler: handler,
}
 
go func() {
srv.Serve(autocert.NewListener(domains...))
}()
 
// Auf ein Interrupt warten
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)

Als Fazit

Go ist eine leistungsstarke Sprache mit einer nahezu universellen Standardbibliothek. Ihre Standardfunktionen sind sehr umfassend, und man kann sie durch Schnittstellen erweitern, was die Entwicklung wirklich zuverlÀssiger HTTP-Server ermöglicht.

Skillbox empfiehlt:

Quelle: habr.com

60GB SSD 8Gb DDR4