Webserverutvikling i Golang - fra enkel til kompleks

Webserverutvikling i Golang - fra enkel til kompleks

For fem år siden begynte jeg utvikle Gophish, ga dette en mulighet til å lære Golang. Jeg innså at Go er et kraftig språk, supplert med mange biblioteker. Go er allsidig: den kan spesielt brukes til å utvikle serversideapplikasjoner uten problemer.

Denne artikkelen handler om å skrive en server i Go. La oss starte med enkle ting som "Hello world!" og avslutte med en applikasjon med følgende funksjoner:

- Bruke Let's Encrypt for HTTPS.
— Jobber som en API-ruter.
— Jobber med mellomvare.
— Behandling av statiske filer.
— Riktig avstengning.

Skillbox anbefaler: Praktisk kurs "Python-utvikler fra bunnen av".

Vi minner om: for alle lesere av "Habr" - en rabatt på 10 000 rubler når du melder deg på et hvilket som helst Skillbox-kurs ved å bruke kampanjekoden "Habr".

Hei Verden!

Du kan opprette en webserver i Go veldig raskt. Her er et eksempel på bruk av en behandler som returnerer "Hei, 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)
}

Etter dette, hvis du kjører programmet og åpner siden localhost, så vil du umiddelbart se teksten "Hei, verden!" (hvis alt fungerer som det skal, selvfølgelig).

Vi bruker behandleren flere ganger senere, men la oss først forstå hvordan alt fungerer.

net/http

Eksemplet brukte pakken net/http, det er det primære verktøyet i Go for å utvikle både servere og HTTP-klienter. For å forstå koden, la oss forstå betydningen av tre viktige elementer: http.Handler, http.ServeMux og http.Server.

HTTP-behandlere

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

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

Det første eksemplet bruker hjelpefunksjonen http.HandleFunc. Den omslutter en annen funksjon, som igjen tar http.ResponseWriter og http.Request inn i ServeHTTP.

Med andre ord, behandlere i Golang presenteres i ett enkelt grensesnitt, noe som gir programmereren mange alternativer. Så for eksempel implementeres mellomvare ved å bruke en behandler, der ServeHTTP først gjør noe og deretter kaller ServeHTTP-metoden til en annen behandler.

Som nevnt ovenfor genererer behandlere ganske enkelt svar på forespørsler. Men hvilken bestemt behandler bør brukes på et bestemt tidspunkt?

Be om ruting

For å gjøre det riktige valget, bruk en HTTP-multiplekser. I en rekke biblioteker kalles det muxer eller ruter, men de er alle det samme. Funksjonen til multiplekseren er å analysere forespørselsbanen og velge riktig behandler.

Hvis du trenger støtte for kompleks ruting, er det bedre å bruke tredjepartsbiblioteker. Noen av de mest avanserte - gorilla/mux и go-chi/chi, gjør disse bibliotekene det mulig å implementere mellombehandling uten problemer. Med deres hjelp kan du konfigurere wildcard-ruting og utføre en rekke andre oppgaver. Fordelen deres er kompatibilitet med standard HTTP-behandlere. Som et resultat kan du skrive enkel kode som kan endres i fremtiden.

Arbeid med komplekse rammeverk i en normal situasjon vil kreve ikke-standardiserte løsninger, og dette kompliserer bruken av standardbehandlere betydelig. For å lage de aller fleste applikasjoner vil en kombinasjon av standardbiblioteket og en enkel ruter være nok.

Forespørselsbehandling

I tillegg trenger vi en komponent som vil "lytte" etter innkommende tilkoblinger og omdirigere alle forespørsler til riktig behandler. http.Server kan enkelt håndtere denne oppgaven.

Det følgende viser at serveren er ansvarlig for alle oppgaver som er relatert til tilkoblingsbehandling. Dette fungerer for eksempel ved å bruke TLS-protokollen. For å implementere http.ListenAndServer-kallet, brukes en standard HTTP-server.

La oss nå se på mer komplekse eksempler.

Legger til Let's Encrypt

Som standard kjører applikasjonen vår over HTTP-protokollen, men det anbefales å bruke HTTPS-protokollen. Dette kan gjøres uten problemer i Go. Hvis du har mottatt et sertifikat og privat nøkkel, er det nok å registrere ListenAndServeTLS med riktige sertifikater og nøkkelfiler.

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

Du kan alltid gjøre det bedre.

La oss kryptere gir gratis sertifikater med automatisk fornyelse. For å bruke tjenesten trenger du en pakke autocert.

Den enkleste måten å konfigurere den på er å bruke autocert.NewListener-metoden i kombinasjon med http.Serve. Metoden lar deg skaffe og oppdatere TLS-sertifikater mens HTTP-serveren behandler forespørsler:

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

Hvis vi åpner i nettleseren example.com, vil vi motta et HTTPS-svar "Hei, verden!"

Hvis du trenger mer detaljert konfigurasjon, bør du bruke autocert.Manager manager. Deretter lager vi vår egen http.Server-forekomst (til nå brukte vi den som standard) og legger til 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 enkel måte å implementere full HTTPS-støtte med automatisk sertifikatfornyelse.

Legger til egendefinerte ruter

Standardruteren som er inkludert i standardbiblioteket er bra, men den er veldig grunnleggende. De fleste applikasjoner krever mer kompleks ruting, inkludert nestede ruter og jokertegn, eller en prosedyre for å angi banemønstre og parametere.

I dette tilfellet er det verdt å bruke pakker gorilla/mux и go-chi/chi. Vi skal lære å jobbe med sistnevnte - et eksempel er vist nedenfor.

Gitt er filen api/v1/api.go som inneholder ruter for vår 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 setter api/vq-prefikset for ruter i hovedfilen.

Vi kan deretter montere denne til hovedruteren vår under api/v1/-prefikset tilbake i hovedapplikasjonen vår:

// 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 enkle å jobbe med komplekse ruter gjør det mulig å forenkle strukturering og vedlikehold av store, komplekse applikasjoner.

Jobber med mellomvare

Staging innebærer å pakke en HTTP-behandler med en annen, noe som gjør det mulig å raskt utføre autentisering, komprimering, logging og flere andre funksjoner.

Som et eksempel, la oss se på http.Handler-grensesnittet; vi bruker det til å skrive en behandler som autentiserer tjenestebrukere.

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 er tredjepartsrutere, for eksempel chi, som lar deg utvide mellomvarefunksjonaliteten.

Arbeid med statiske filer

Go-standardbiblioteket inkluderer muligheter for å jobbe med statisk innhold, inkludert bilder, JavaScript og CSS-filer. De kan nås via http.FileServer-funksjonen. Den returnerer en behandler som serverer filer fra en bestemt 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 er definitivt verdt å huske at http.Dir viser innholdet i katalogen hvis den ikke inneholder hovedfilen index.html. I dette tilfellet, for å forhindre at katalogen blir kompromittert, bør du bruke pakken unindexed.

Riktig avstengning

Go har også en funksjon kalt grasiøs avslutning av HTTP-serveren. Dette kan gjøres ved å bruke Shutdown()-metoden. Serveren startes i en goroutine, og deretter lyttes kanalen for å motta et avbruddssignal. Så snart signalet er mottatt, slår serveren seg av, men ikke umiddelbart, men etter noen 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 konklusjon

Go er et kraftig språk med et nesten universelt standardbibliotek. Standardfunksjonene er veldig brede, og de kan forbedres ved hjelp av grensesnitt - dette lar deg utvikle virkelig pålitelige HTTP-servere.

Skillbox anbefaler:

Kilde: www.habr.com

Legg til en kommentar