Webserver-Entwicklung in Golang – von einfach bis komplex

Webserver-Entwicklung in Golang – von einfach bis komplex

Vor fünf Jahren habe ich angefangen Gophish entwickelnDies bot die Gelegenheit, Golang zu lernen. Mir wurde klar, dass Go eine mächtige Sprache ist, die durch viele Bibliotheken ergänzt wird. Go ist vielseitig: Insbesondere lassen sich damit problemlos serverseitige Anwendungen entwickeln.

In diesem Artikel geht es um das Schreiben eines Servers in Go. Beginnen wir mit einfachen Dingen wie „Hallo Welt!“ und enden mit einer Anwendung mit den folgenden Funktionen:

- Verwendung von Let's Encrypt für HTTPS.
— Arbeitet als API-Router.
— Arbeiten mit Middleware.
— Verarbeitung statischer Dateien.
— Korrektes Herunterfahren.

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

Erinnerung: für alle Leser von „Habr“ – ein Rabatt von 10 Rubel bei der Anmeldung zu einem beliebigen Skillbox-Kurs mit dem Aktionscode „Habr“.

Hallo Welt!

Sie können in Go sehr schnell einen Webserver 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)
}

Danach führen Sie die Anwendung aus und öffnen die Seite localhost, dann sehen Sie sofort den Text „Hallo Welt!“ (natürlich nur, wenn alles richtig funktioniert).

Wir werden den Handler später mehrmals verwenden, aber zunächst wollen wir verstehen, wie alles funktioniert.

net/http

Das Beispiel verwendete das Paket net/httpEs ist das wichtigste Tool in Go zum Entwickeln von Servern und HTTP-Clients. Um den Code zu verstehen, verstehen wir die Bedeutung von drei wichtigen Elementen: http.Handler, http.ServeMux und http.Server.

HTTP-Handler

Wenn wir eine Anfrage erhalten, analysiert der Handler diese und generiert eine Antwort. Handler in Go werden wie folgt implementiert:

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

Das erste Beispiel verwendet die Hilfsfunktion http.HandleFunc. Es umschließt eine weitere Funktion, die wiederum http.ResponseWriter und http.Request in ServeHTTP übernimmt.

Mit anderen Worten: Handler in Golang werden in einer einzigen Schnittstelle dargestellt, die dem Programmierer viele Optionen bietet. So wird beispielsweise Middleware mithilfe eines Handlers implementiert, bei dem ServeHTTP zunächst etwas ausführt und dann die ServeHTTP-Methode eines anderen Handlers aufruft.

Wie oben erwähnt, generieren Handler lediglich Antworten auf Anfragen. Aber welcher bestimmte Handler sollte zu einem bestimmten Zeitpunkt verwendet werden?

Routing anfordern

Um die richtige Wahl zu treffen, verwenden Sie einen HTTP-Multiplexer. In einer Reihe von Bibliotheken wird es Muxer oder Router genannt, aber sie sind alle dasselbe. Die Funktion des Multiplexers besteht darin, den Anforderungspfad zu analysieren und den geeigneten Handler auszuwählen.

Wenn Sie Unterstützung für komplexes Routing benötigen, ist es besser, Bibliotheken von Drittanbietern zu verwenden. Einige der fortschrittlichsten - Gorilla/Mux и go-chi/chiDiese Bibliotheken ermöglichen eine problemlose Umsetzung der Zwischenverarbeitung. Mit ihrer Hilfe können Sie Wildcard-Routing konfigurieren und eine Reihe anderer Aufgaben ausführen. Ihr Vorteil ist die Kompatibilität mit Standard-HTTP-Handlern. Dadurch können Sie einfachen Code schreiben, der in Zukunft geändert werden kann.

Die Arbeit mit komplexen Frameworks erfordert in einer normalen Situation nicht standardmäßige Lösungen, was die Verwendung von Standardhandlern erheblich erschwert. Um die überwiegende Mehrheit der Anwendungen zu erstellen, reicht eine Kombination aus der Standardbibliothek und einem einfachen Router aus.

Abfrageverarbeitung

Darüber hinaus benötigen wir eine Komponente, die auf eingehende Verbindungen „lauscht“ und alle Anfragen an den richtigen Handler umleitet. http.Server kann diese Aufgabe problemlos bewältigen.

Im Folgenden wird gezeigt, dass der Server für alle Aufgaben verantwortlich ist, die mit der Verbindungsverarbeitung zusammenhängen. Dies funktioniert beispielsweise über das TLS-Protokoll. Zur Implementierung des http.ListenAndServer-Aufrufs wird ein Standard-HTTP-Server verwendet.

Schauen wir uns nun komplexere Beispiele an.

Let's Encrypt hinzufügen

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

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

Man kann es immer besser machen.

Lass uns verschlüsseln bietet kostenlose Zertifikate mit automatischer Verlängerung. Um den Service nutzen zu können, benötigen Sie ein Paket autocert.

Der einfachste Weg, es zu konfigurieren, ist die Verwendung der autocert.NewListener-Methode in Kombination mit http.Serve. Mit dieser Methode können Sie TLS-Zertifikate abrufen und aktualisieren, während der HTTP-Server Anforderungen verarbeitet:

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

Wenn wir im Browser öffnen example.comerhalten wir eine HTTPS-Antwort „Hello, world!“

Wenn Sie eine detailliertere Konfiguration benötigen, sollten Sie den Manager autocert.Manager verwenden. Dann erstellen wir unsere eigene http.Server-Instanz (bisher haben wir sie standardmäßig verwendet) und fügen den Manager zum TLSConfig-Server 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, die vollständige HTTPS-Unterstützung mit automatischer Zertifikatserneuerung zu implementieren.

Benutzerdefinierte Routen hinzufügen

Der in der Standardbibliothek enthaltene Standardrouter ist gut, aber sehr einfach. Die meisten Anwendungen erfordern komplexeres Routing, einschließlich verschachtelter Routen und Wildcard-Routen, oder ein Verfahren zum Festlegen von Pfadmustern und Parametern.

In diesem Fall lohnt es sich, Pakete zu verwenden Gorilla/Mux и go-chi/chi. Wir werden lernen, mit Letzterem zu arbeiten – ein Beispiel ist unten dargestellt.

Gegeben ist die Datei api/v1/api.go, 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 legen das API/VQ-Präfix für Routen in der Hauptdatei fest.

Wir können dies dann in unserer Hauptanwendung unter dem Präfix api/v1/ auf unserem Hauptrouter mounten:

// 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 Arbeit von Go mit komplexen Routen ermöglicht es, die Strukturierung und Wartung großer, komplexer Anwendungen zu vereinfachen.

Arbeiten mit Middleware

Beim Staging geht es darum, einen HTTP-Handler mit einem anderen zu verpacken, was die schnelle Durchführung von Authentifizierung, Komprimierung, Protokollierung und mehreren anderen Funktionen ermöglicht.

Schauen wir uns als Beispiel die http.Handler-Schnittstelle an; wir werden sie verwenden, um einen Handler zu schreiben, der Dienstbenutzer authentifiziert.

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
        }
        // Assuming authentication passed, run the original handler
        next.ServeHTTP(w, r)
    })
}

Es gibt Router von Drittanbietern wie Chi, mit denen Sie die Middleware-Funktionalität erweitern können.

Arbeiten mit statischen Dateien

Die Go-Standardbibliothek umfasst Funktionen zum Arbeiten mit statischen Inhalten, einschließlich Bildern, JavaScript- und CSS-Dateien. Auf sie kann über die Funktion http.FileServer zugegriffen werden. Es gibt einen Handler zurück, der Dateien aus einem bestimmten Verzeichnis bereitstellt.

func NewRouter() http.Handler {
    router := chi.NewRouter()
    r.Get("/{name}", HelloName)
 
// Настройка раздачи статических файлов
staticPath, _ := filepath.Abs("../../static/")
fs := http.FileServer(http.Dir(staticPath))
    router.Handle("/*", fs)
    
    return r

Es ist unbedingt zu beachten, dass http.Dir den Inhalt des Verzeichnisses anzeigt, wenn es nicht die Hauptdatei index.html enthält. Um zu verhindern, dass das Verzeichnis kompromittiert wird, sollten Sie in diesem Fall das Paket verwenden unindexed.

Korrektes Herunterfahren

Go verfügt außerdem über eine Funktion namens „Graceful Shutdown“ des HTTP-Servers. Dies kann mit der Methode Shutdown() erfolgen. Der Server wird in einer Goroutine gestartet und dann wird der Kanal auf den Empfang eines Interrupt-Signals überwacht. Sobald das Signal empfangen wird, schaltet sich der Server ab, jedoch nicht sofort, sondern nach einigen Sekunden.

handler := server.NewRouter()
srv := &http.Server{
    Handler: handler,
}
 
go func() {
srv.Serve(autocert.NewListener(domains...))
}()
 
// Wait for an interrupt
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
 
// Attempt a graceful shutdown
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(ctx)

Als eine Schlussfolgerung

Go ist eine leistungsstarke Sprache mit einer nahezu universellen Standardbibliothek. Seine Standardfunktionen sind sehr umfangreich und können mithilfe von Schnittstellen erweitert werden – so können Sie wirklich zuverlässige HTTP-Server entwickeln.

Skillbox empfiehlt:

Source: habr.com

Kommentar hinzufügen