De ontwikkeling van webservers in Golang – van eenvoudig naar complex

De ontwikkeling van webservers in Golang – van eenvoudig naar complex

Vijf jaar geleden begon ik Gophish te ontwikkelen, dit gaf me de mogelijkheid om Golang te leren. Ik realiseerde me dat Go een krachtige taal is, waarvan de mogelijkheden worden aangevuld met veel bibliotheken. Go is veelzijdig: je kunt er zonder problemen serverapplicaties mee ontwikkelen.

Dit artikel is gewijd aan het schrijven van een server in Go. Laten we beginnen met eenvoudige dingen, zoals "Hello world!", en eindigen met een applicatie met functies zoals:

— Gebruik van Let’s Encrypt voor HTTPS.
— Fungeren als API-router.
— Werken met middleware.
— Verwerken van statische bestanden.
— Correct afsluiten.

Skillbox raadt aan: Praktische cursus «Python-ontwikkelaar vanaf nul».

Ter herinnering: voor alle lezers van «Habr» — een korting van 10.000 roebel bij inschrijving voor elke cursus van Skillbox met de promocode «Habr».

Hallo, wereld!

Een webserver op Go kan heel snel worden gemaakt. Hier is een voorbeeld van een handler die de eerder beloofde "Hallo, wereld!" teruggeeft.

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

Als je dit vervolgens uitvoert, en de pagina opent localhost, dan zie je meteen de tekst "Hallo, wereld!" (natuurlijk, als alles goed werkt).

Daarna zullen we de handler herhaaldelijk gebruiken, maar laten we eerst begrijpen hoe alles in elkaar zit.

net/http

In het voorbeeld werd het pakket gebruikt net/http, dit is het belangrijkste hulpmiddel in Go voor zowel server- als HTTP-clientontwikkeling. Om de code te begrijpen, laten we de betekenis van drie belangrijke elementen bekijken: http.Handler, http.ServeMux en http.Server.

HTTP-handlers

Wanneer we een verzoek ontvangen, analyseert de handler het en vormt een antwoord. Handlers in Go zijn als volgt geïmplementeerd:

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

In het eerste voorbeeld wordt de hulpfunctie http.HandleFunc gebruikt. Deze wikkelt een andere functie in, die op haar beurt http.ResponseWriter en http.Request in ServeHTTP accepteert.

Met andere woorden, handlers in Golang worden gepresenteerd als een enkele interface, wat de programmeur veel mogelijkheden biedt. Zo is middleware bijvoorbeeld geïmplementeerd met behulp van een handler, waar ServeHTTP eerst iets doet en dan de ServeHTTP-methode van een andere handler aanroept.

Zoals eerder aangegeven, vormen de handlers gewoon de antwoorden op verzoeken. Maar welke handler moet je op een bepaald moment gebruiken?

Routing van verzoeken

Om de juiste keuze te maken, gebruik je een HTTP-multiplexer. In sommige bibliotheken wordt dit een muxer of router genoemd, maar het is allemaal hetzelfde. De functie van de multiplexer is het analyseren van het pad van het verzoek en het kiezen van de bijbehorende handler.

Als er complexe routingondersteuning nodig is, kun je beter externe bibliotheken gebruiken. Enkele van de meest geavanceerde zijn gorilla/mux en go-chi/chi, deze bibliotheken maken het mogelijk om middleware eenvoudig te implementeren. Hiermee kun je wildcard-routing instellen en een aantal andere taken uitvoeren. Hun voordeel is de compatibiliteit met standaard HTTP-handlers. Hierdoor kun je eenvoudige code schrijven met de mogelijkheid om deze in de toekomst te wijzigen.

Werken met complexe frameworks vereist in de regel niet-standaard oplossingen, wat het gebruik van de standaard handlers aanzienlijk bemoeilijkt. Voor het merendeel van de applicaties is een combinatie van de standaardbibliotheek en een eenvoudige router voldoende.

Verwerkingsverzoeken

Bovendien hebben we een component nodig die 'luistert' naar binnenkomende verbindingen en alle verzoeken naar de juiste handler doorverbindt. Deze taak kan probleemloos door http.Server worden uitgevoerd.

Hieronder is te zien dat de server verantwoordelijk is voor alle taken die verband houden met het verwerken van verbindingen. Dit omvat bijvoorbeeld het werken met het TLS-protocol. Voor de implementatie wordt de standaard HTTP-server gebruikt via http.ListenAndServer.

Laten we nu naar meer complexe voorbeelden kijken.

Toevoegen van Let’s Encrypt

Standaard werkt onze applicatie via het HTTP-protocol, maar het wordt aanbevolen om het HTTPS-protocol te gebruiken. In Go is dit geen probleem. Als je een certificaat en een privésleutel hebt ontvangen, hoef je alleen maar ListenAndServeTLS aan te roepen met de juiste certificaat- en sleutelbestanden.

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

Het kan altijd beter.

Let’s Encrypt biedt gratis certificaten met de mogelijkheid voor automatische vernieuwing. Om gebruik te maken van de service, is het pakket nodig autocert.

De eenvoudigste manier om het in te stellen is door de autocert.NewListener-methode te gebruiken in combinatie met http.Serve. Deze methode maakt het mogelijk om TLS-certificaten te verkrijgen en te vernieuwen terwijl de HTTP-server verzoeken verwerkt:

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

Als we het in de browser openen example.com, krijgen we een HTTPS-antwoord "Hello, world!".

Als een meer gedetailleerde configuratie nodig is, kan de autocert.Manager worden gebruikt. Vervolgens creëren we een eigen instantie van http.Server (tot nu toe hebben we de standaardversie gebruikt) en voegen de manager toe aan de server TLSConfig:

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("", "")

Dit is een eenvoudige manier om volledige ondersteuning voor HTTPS met automatische certificaatvernieuwing te implementeren.

Toevoegen van aangepaste routes

De standaardrouter die in de standaardbibliotheek is opgenomen, is goed, maar erg eenvoudig. In de meeste applicaties is geavanceerdere routering nodig, inclusief geneste en wildcard-routes of het instellen van sjablonen en padparameters.

In dat geval is het verstandig om pakketten te gebruiken gorilla/mux en go-chi/chi. Met de laatste leren we werken — hieronder staat een voorbeeld.

Gegeven: het bestand api/v1/api.go, dat routes voor onze API bevat:

/ 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
}

We stellen een voorvoegsel api/vq in voor de routes in het hoofdbestand.

We kunnen dit dan onder de api/v1/ voorprefix in onze hoofdapplicatie koppelen:

// 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())

De eenvoud van werken met complexe routes in Go maakt het mogelijk om de structurering van grote complexe applicaties te vereenvoudigen.

Werken met middleware

Bij tussenverwerking wordt één HTTP-handler door een andere gewikkeld, waardoor het mogelijk is om snel authenticatie, compressie, logging en andere functies uit te voeren.

Als voorbeeld bekijken we de http.Handler-interface en schrijven we een handler voor gebruikersauthenticatie.

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
        }
        // Aangenomen dat authentificatie is geslaagd, voer de originele handler uit
        next.ServeHTTP(w, r)
    })
}

Er zijn externe routers, zoals chi, die de functionaliteit van middleware kunnen uitbreiden.

Werken met statische bestanden

De standaardbibliotheek van Go bevat mogelijkheden voor het werken met statische inhoud, inclusief afbeeldingen, evenals JavaScript- en CSS-bestanden. Toegang tot deze bestanden kan worden verkregen via de functie http.FileServer. Deze retourneert een handler die bestanden uit een bepaalde directory serveert.

func NewRouter() http.Handler {
    router := chi.NewRouter()
    r.Get("/{name}", HelloName)
 
// Configuratie voor het serveren van statische bestanden
staticPath, _ := filepath.Abs("../../static/")
fs := http.FileServer(http.Dir(staticPath))
    router.Handle("/*", fs)
    
    return r

Het is belangrijk om te onthouden dat http.Dir de inhoud van de directory alleen weergeeft als er geen hoofdindexbestand index.html in de map aanwezig is. In dit geval, om te voorkomen dat de directory gecompromitteerd wordt, moet het pakket worden gebruikt. niet-geïncludeerd.

Correcte afsluiting

In Go is er ook een functie voor correcte afsluiting van de HTTP-server. Dit kan gedaan worden met de Shutdown()-methode. De server wordt gestart in een goroutine en vervolgens wordt een kanaal afgeluisterd om een onderbrekingssignaal te ontvangen. Zodra het signaal wordt ontvangen, schakelt de server niet onmiddellijk uit, maar na een paar seconden.

handler := server.NewRouter()
srv := &http.Server{
    Handler: handler,
}
 
go func() {
srv.Serve(autocert.NewListener(domains...))
}()
 
// Wacht op een onderbreking
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
 
// Probeer een nette afsluiting
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(ctx)

Tot slot

Go is een krachtige taal met een bijna universele standaardbibliotheek. De standaardfunctionaliteit is behoorlijk breed en kan worden versterkt door interfaces - dit maakt het mogelijk om echt betrouwbare HTTP-servers te ontwikkelen.

Skillbox raadt aan:

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster