Webbserverutveckling i Golang - från enkel till komplex

Webbserverutveckling i Golang - från enkel till komplex

För fem år sedan började jag utveckla Gophish, detta gav en möjlighet att lära sig Golang. Jag insåg att Go är ett kraftfullt språk, kompletterat av många bibliotek. Go är mångsidig: i synnerhet kan den användas för att utveckla applikationer på serversidan utan problem.

Den här artikeln handlar om att skriva en server i Go. Låt oss börja med enkla saker som "Hello world!" och avsluta med en applikation med följande funktioner:

- Använda Let's Encrypt för HTTPS.
— Arbetar som en API-router.
— Arbeta med middleware.
— Bearbetning av statiska filer.
— Korrekt avstängning.

Skillbox rekommenderar: Praktisk kurs "Python-utvecklare från grunden".

Påminnelse: för alla läsare av "Habr" - en rabatt på 10 000 rubel när du anmäler dig till någon Skillbox-kurs med hjälp av "Habr"-kampanjkoden.

Hej världen!

Du kan skapa en webbserver i Go mycket snabbt. Här är ett exempel på att använda en hanterare som returnerar "Hej världen!" som utlovats ovan.

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

Efter detta, om du kör programmet och öppnar sidan lokalvärd, då ser du omedelbart texten "Hej, värld!" (om allt fungerar som det ska förstås).

Vi kommer att använda hanteraren flera gånger senare, men låt oss först förstå hur allt fungerar.

net/http

I exemplet användes paketet net/http, det är det primära verktyget i Go för att utveckla både servrar och HTTP-klienter. För att förstå koden, låt oss förstå innebörden av tre viktiga element: http.Handler, http.ServeMux och http.Server.

HTTP-hanterare

När vi får en förfrågan analyserar hanteraren den och genererar ett svar. Hanterare i Go implementeras enligt följande:

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

Det första exemplet använder hjälpfunktionen http.HandleFunc. Den omsluter en annan funktion, som i sin tur tar http.ResponseWriter och http.Request till ServeHTTP.

Med andra ord, hanterare i Golang presenteras i ett enda gränssnitt, vilket ger programmeraren många alternativ. Så till exempel implementeras mellanprogram med hjälp av en hanterare, där ServeHTTP först gör något och sedan anropar ServeHTTP-metoden för en annan hanterare.

Som nämnts ovan genererar hanterare helt enkelt svar på förfrågningar. Men vilken speciell hanterare ska användas vid en viss tidpunkt?

Begär routing

För att göra rätt val, använd en HTTP-multiplexer. I ett antal bibliotek kallas det muxer eller router, men de är alla samma sak. Multiplexerns funktion är att analysera sökvägen för begäran och välja lämplig hanterare.

Om du behöver stöd för komplex routing är det bättre att använda tredjepartsbibliotek. Några av de mest avancerade - gorilla/mux и go-chi/chi, dessa bibliotek gör det möjligt att implementera mellanliggande bearbetning utan problem. Med deras hjälp kan du konfigurera routing med jokertecken och utföra ett antal andra uppgifter. Deras fördel är kompatibilitet med vanliga HTTP-hanterare. Som ett resultat kan du skriva enkel kod som kan ändras i framtiden.

Att arbeta med komplexa ramverk i en normal situation kommer att kräva icke-standardiserade lösningar, och detta komplicerar avsevärt användningen av standardhanterare. För att skapa de allra flesta applikationer räcker det med en kombination av standardbiblioteket och en enkel router.

Frågebearbetning

Dessutom behöver vi en komponent som "lyssnar" efter inkommande anslutningar och omdirigerar alla förfrågningar till rätt hanterare. http.Server kan enkelt hantera denna uppgift.

Följande visar att servern är ansvarig för alla uppgifter som är relaterade till anslutningsbearbetning. Detta fungerar till exempel med TLS-protokollet. För att implementera http.ListenAndServer-anropet används en standard HTTP-server.

Låt oss nu titta på mer komplexa exempel.

Lägger till Let's Encrypt

Som standard körs vår applikation över HTTP-protokollet, men det rekommenderas att använda HTTPS-protokollet. Detta kan göras utan problem i Go. Om du har fått ett certifikat och en privat nyckel räcker det med att registrera ListenAndServeTLS med rätt certifikat och nyckelfiler.

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

Du kan alltid göra bättre.

Låt oss kryptera ger gratis certifikat med automatisk förnyelse. För att kunna använda tjänsten behöver du ett paket autocert.

Det enklaste sättet att konfigurera det är att använda metoden autocert.NewListener i kombination med http.Serve. Metoden låter dig erhålla och uppdatera TLS-certifikat medan HTTP-servern bearbetar begäranden:

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

Om vi ​​öppnar i webbläsaren example.com, kommer vi att få ett HTTPS-svar "Hej världen!"

Om du behöver mer detaljerad konfiguration bör du använda autocert.Manager-hanteraren. Sedan skapar vi vår egen http.Server-instans (tills nu använde vi den som standard) och lägger till managern till TLSConfig-servern:

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

Detta är ett enkelt sätt att implementera fullt HTTPS-stöd med automatisk certifikatförnyelse.

Lägger till anpassade rutter

Standardroutern som ingår i standardbiblioteket är bra, men den är väldigt enkel. De flesta applikationer kräver mer komplex routing, inklusive kapslade rutter och jokertecken, eller en procedur för att ställa in sökvägsmönster och parametrar.

I det här fallet är det värt att använda paket gorilla/mux и go-chi/chi. Vi kommer att lära oss hur man arbetar med det senare - ett exempel visas nedan.

Givet är filen api/v1/api.go som innehåller rutter för vårt API:

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

Vi ställer in api/vq-prefixet för rutter i huvudfilen.

Vi kan sedan montera detta till vår huvudrouter under api/v1/-prefixet tillbaka i vår huvudapplikation:

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

Gos lätthet att arbeta med komplexa rutter gör det möjligt att förenkla strukturering och underhåll av stora, komplexa applikationer.

Jobbar med middleware

Staging innebär att linda en HTTP-hanterare med en annan, vilket gör det möjligt att snabbt utföra autentisering, komprimering, loggning och flera andra funktioner.

Som ett exempel, låt oss titta på http.Handler-gränssnittet, vi kommer att använda det för att skriva en hanterare som autentiserar tjänstanvändare.

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

Det finns tredjepartsroutrar, som chi, som låter dig utöka mellanvarufunktionaliteten.

Arbeta med statiska filer

Go-standardbiblioteket innehåller funktioner för att arbeta med statiskt innehåll, inklusive bilder, JavaScript och CSS-filer. De kan nås via http.FileServer-funktionen. Den returnerar en hanterare som serverar filer från en specifik katalog.

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

Det är definitivt värt att komma ihåg att http.Dir visar innehållet i katalogen om den inte innehåller huvudfilen index.html. I det här fallet, för att förhindra att katalogen äventyras, bör du använda paketet unindexed.

Korrekt avstängning

Go har också en funktion som kallas graciös avstängning av HTTP-servern. Detta kan göras med metoden Shutdown() . Servern startas i en goroutine och sedan lyssnas kanalen för att ta emot en avbrottssignal. Så snart signalen tas emot stängs servern av, men inte omedelbart, utan efter några sekunder.

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)

Som en slutsats

Go är ett kraftfullt språk med ett nästan universellt standardbibliotek. Dess standardfunktioner är mycket breda och de kan förbättras med hjälp av gränssnitt - detta låter dig utveckla verkligt pålitliga HTTP-servrar.

Skillbox rekommenderar:

Källa: will.com

Lägg en kommentar