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 und 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 und 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-Server đŸ”„ Kaufen Sie zuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster