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.
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.
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:
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:
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.
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.