Udvikling af webservere i Golang - fra simpelt til komplekst

Udvikling af webservere i Golang - fra simpelt til komplekst

For fem år siden startede jeg udvikle Gophish, det gjorde det muligt at lære Golang. Jeg indså, at Go er et kraftfuldt sprog, som suppleres af mange biblioteker. Go er alsidig: Især kan du nemt udvikle server-side applikationer med det.

Denne artikel handler om at skrive en server i Go. Lad os starte med simple ting som "Hej verden!" og ende op med en applikation med disse funktioner:

- Brug af Let's Encrypt til HTTPS.
- Arbejde som en API-router.
- Arbejde med middleware.
- Håndtering af statiske filer.
- Korrekt nedlukning.

Skillbox anbefaler: Praktisk kursus "Python-udvikler fra bunden".

Påmindelse: for alle læsere af "Habr" - en rabat på 10 rubler ved tilmelding til ethvert Skillbox-kursus ved hjælp af "Habr"-kampagnekoden.

Hej verden!

Det kan være meget hurtigt at oprette en webserver i Go. Her er et eksempel på brug af en handler, der returnerer "Hej, verden!" lovet ovenfor.

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 det, hvis du kører programmet og åbner siden localhost, så vil du straks se teksten "Hej, verden!" (selvfølgelig hvis alt fungerer korrekt).

Vi vil bruge handleren gentagne gange i det følgende, men lad os først forstå, hvordan det hele fungerer.

net/http

Eksemplet brugte pakken net/http, er Gos primære værktøj til at udvikle både servere og HTTP-klienter. For at forstå koden, lad os forstå betydningen af ​​tre vigtige elementer: http.Handler, http.ServeMux og http.Server.

HTTP-handlere

Når vi modtager en anmodning, analyserer behandleren den og genererer et svar. Handlere i Go implementeres som følger:

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

Det første eksempel bruger hjælpefunktionen http.HandleFunc. Det ombryder en anden funktion, som igen accepterer http.ResponseWriter og http.Request i ServeHTTP.

Med andre ord er handlere i Golang repræsenteret af en enkelt grænseflade, hvilket giver en masse muligheder for programmøren. Så for eksempel implementeres middleware ved hjælp af en handler, hvor ServeHTTP først gør noget og derefter kalder ServeHTTP-metoden for en anden handler.

Som nævnt ovenfor danner behandlere blot svar på anmodninger. Men hvilken handler skal bruges på et bestemt tidspunkt?

Anmod om routing

Brug HTTP-multiplekseren for at træffe det rigtige valg. I en række biblioteker kaldes det muxer eller router, men de er alle ens. Multiplexerens funktion er at analysere anmodningsstien og vælge den passende behandler.

Hvis du har brug for support til kompleks routing, er det bedre at bruge tredjepartsbiblioteker. En af de mest avancerede gorilla/mux и go-chi/chi, gør disse biblioteker det muligt at implementere mellembehandling uden problemer. Med deres hjælp kan du konfigurere wildcard-routing og udføre en række andre opgaver. Deres fordel er kompatibilitet med standard HTTP-handlere. Som et resultat kan du skrive simpel kode, der kan ændres i fremtiden.

At arbejde med komplekse rammer i en normal situation vil kræve ikke helt standardløsninger, og det komplicerer i høj grad brugen af ​​standardhandlere. Kombinationen af ​​standardbiblioteket og en simpel router vil være tilstrækkelig til at skabe langt de fleste applikationer.

Forespørgselsbehandling

Derudover har vi brug for en komponent, der vil "lytte" efter indgående forbindelser og omdirigere alle anmodninger til den korrekte handler. http.Server kan nemt klare denne opgave.

Det følgende viser, at serveren er ansvarlig for alle opgaver relateret til håndtering af forbindelser. Dette er for eksempel arbejde med TLS-protokollen. En standard HTTP-server bruges til at implementere http.ListenAndServer-kaldet.

Lad os nu se på mere komplekse eksempler.

Tilføjelse af Let's Encrypt

Som standard kører vores applikation over HTTP-protokollen, men det anbefales at bruge HTTPS-protokollen. I Go kan dette gøres uden problemer. Hvis du har modtaget et certifikat og en privat nøgle, så er det nok at skrive ListenAndServeTLS med de korrekte certifikater og nøglefiler.

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

Du kan altid gøre det bedre.

Lad os kryptere giver gratis certifikater med mulighed for automatisk fornyelse. For at bruge tjenesten skal du have en pakke autocert.

Den nemmeste måde at konfigurere det på er at bruge autocert.NewListener metoden i kombination med http.Serve. Metoden giver dig mulighed for at modtage og forny TLS-certifikater, mens HTTP-serveren behandler anmodninger:

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

Hvis vi åbner i browseren example.com, får vi et HTTPS-svar "Hej, verden!".

Hvis du har brug for mere detaljeret konfiguration, skal du bruge autocert.Manager. Derefter opretter vi vores egen http.Server-instans (indtil videre har vi brugt den som standard) og tilføjer manageren til TLSConfig-serveren:

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

Dette er en nem måde at implementere fuld HTTPS-understøttelse med automatisk certifikatfornyelse.

Tilføjelse af brugerdefinerede ruter

Standardrouteren inkluderet i standardbiblioteket er fin, men meget grundlæggende. De fleste applikationer har brug for mere kompleks routing, herunder indlejrede ruter og jokertegn, eller indstilling af mønstre og stiparametre.

I dette tilfælde skal du bruge pakker gorilla/mux и go-chi/chi. Vi vil lære at arbejde med sidstnævnte - et eksempel er vist nedenfor.

Givet er api/v1/api.go-filen, der indeholder ruterne til vores 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 indstiller api/vq-præfikset for ruterne i hovedfilen.

Vi kan derefter montere dette på vores hovedrouter under api/v1/-præfikset tilbage i vores hovedapplikation:

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

Den lette at arbejde med komplekse ruter i Go gør det muligt at forenkle struktureringen og vedligeholdelsen af ​​store komplekse applikationer.

Arbejder med middleware

I tilfælde af mellembehandling bruges en HTTP-handler med en anden, hvilket gør det muligt hurtigt at udføre autentificering, komprimering, logning og nogle andre funktioner.

Lad os som et eksempel overveje http.Handler-grænsefladen, med dens hjælp vil vi skrive en handler med godkendelse af tjenestebrugere.

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

Der er tredjepartsroutere, såsom chi, der giver dig mulighed for at udvide funktionaliteten af ​​mellembehandling.

Arbejde med statiske filer

Gos standardbibliotek indeholder faciliteter til at arbejde med statisk indhold, herunder billeder, samt JavaScript- og CSS-filer. De kan tilgås via http.FileServer-funktionen. Det returnerer en handler, der distribuerer filer fra en bestemt mappe.

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

Husk at http.Dir viser indholdet af mappen, hvis den ikke indeholder hovedfilen index.html. I dette tilfælde bør du bruge pakken for at forhindre mappen i at blive kompromitteret unindexed.

Korrekt nedlukning

Go har også en sådan funktion som en yndefuld lukning af HTTP-serveren. Dette kan gøres ved hjælp af Shutdown() metoden. Serveren startes i en goroutine, og derefter lyttes der efter kanalen for at modtage et afbrydelsessignal. Så snart signalet modtages, slukker serveren, men ikke med det samme, men efter et par 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 konklusion

Go er et kraftfuldt sprog med et næsten universelt standardbibliotek. Dens standardfunktioner er meget brede, og du kan styrke dem ved hjælp af grænseflader - dette giver dig mulighed for at udvikle virkelig pålidelige HTTP-servere.

Skillbox anbefaler:

Kilde: www.habr.com

Tilføj en kommentar