
Acum cinci ani am început , aceasta mi-a oferit ocazia să învăț Golang. Am realizat că Go este un limbaj puternic, a cărui capacitate este completată de numeroase biblioteci. Go este versatil: de exemplu, cu ajutorul său se pot dezvolta aplicații server fără probleme.
Acest articol este dedicat scrierii unui server în Go. Să începem cu lucruri simple, precum „Hello world!”, și să terminăm cu o aplicație care are următoarele funcționalități:
— Utilizarea Let’s Encrypt pentru HTTPS.
— Funcționarea ca API-router.
— Lucrul cu middleware.
— Gestionarea fișierelor statice.
— Încetarea corectă a funcționării.
Skillbox recomandă: Curs practic .
Vă reamintim: pentru toți cititorii „Habr” — reducere de 10.000 de ruble la înscrierea la orice curs Skillbox cu codul de promovare „Habr”.
Hello, world!
Crearea unui server web în Go poate fi realizată foarte rapid. Iată un exemplu de utilizare a unui handler care returnează celebrul „Hello, world!” menționat mai devreme.
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)
}După aceasta, dacă porniți aplicația și deschideți pagina , veți vedea imediat textul „Hello, world!” (desigur, dacă totul funcționează corect).
Mai departe, vom utiliza în repetate rânduri handler-ul, dar mai întâi haideți să înțelegem cum funcționează totul.
net/http
În exemplu a fost folosit pachetul , acesta este instrumentul principal în Go pentru dezvoltarea atât a serverelor, cât și a clienților HTTP. Pentru a înțelege codul, haideți să analizăm semnificația a trei elemente importante: http.Handler, http.ServeMux și http.Server.
HTTP-handlers
Atunci când primim o cerere, handler-ul o analizează și formează un răspuns. Handlers în Go sunt implementați după cum urmează:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}În primul exemplu, se folosește funcția auxiliară http.HandleFunc. Aceasta învăluie o altă funcție, care la rândul ei acceptă http.ResponseWriter și http.Request în ServeHTTP.
Cu alte cuvinte, handlers în Golang sunt reprezentate de o interfață unică, ceea ce oferă numeroase oportunități pentru programator. De exemplu, middleware este implementat printr-un handler, unde ServeHTTP face întâi ceva, apoi apelează metoda ServeHTTP a altui handler.
După cum s-a menționat mai sus, handlers doar formează răspunsuri la cereri. Dar ce handler ar trebui folosit în fiecare moment particular?
Rutare cereri
Pentru a face alegerea corectă, folosiți multiplexorul HTTP. În unele biblioteci este numit muxer sau router, dar toate acestea sunt la fel. Funcția multiplexorului constă în analiza căii cererii și alegerea handler-ului corespunzător.
Dacă aveți nevoie de suport pentru rutare complexă, este mai bine să folosiți biblioteci terțe. Unele dintre cele mai avansate sunt și , aceste biblioteci oferă posibilitatea de a implementa procesare intermediară fără probleme. Cu ajutorul lor, puteți configura rutare wildcard și îndeplini o serie de alte sarcini. Avantajul lor este compatibilitatea cu handlerii HTTP standard. Ca urmare, puteți scrie un cod simplu cu posibilitatea de modificare ulterioară.
Lucrul cu framework-uri complexe în situații obișnuite va necesita soluții care nu sunt complet standard, ceea ce îngreunează semnificativ utilizarea handlerilor implicite. Pentru a crea majoritatea aplicațiilor, este suficientă combinația dintre biblioteca implicită și un router simplu.
Procesarea cererilor
În plus, avem nevoie de un component care să „asculte” conexiunile de intrare și să redirecționeze toate cererile către handlerul corect. Această sarcină este îndeplinită cu ușurință de http.Server.
Mai jos este prezentat că serverul este responsabil pentru toate sarcinile legate de procesarea conexiunilor. Acestea includ, de exemplu, lucrul pe protocolul TLS. Pentru a apela http.ListenAndServer se folosește serverul HTTP standard.
Acum să examinăm exemple mai complexe.
Adăugarea Let’s Encrypt
Implicit, aplicația noastră funcționează pe protocolul HTTP, dar este recomandat să folosiți protocolul HTTPS. În Go, acest lucru se poate face fără probleme. Dacă ați obținut un certificat și o cheie privată, este suficient să specificați ListenAndServeTLS cu fișierele corecte ale certificatului și cheii.
http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil)Întotdeauna există o modalitate mai bună.
oferă certificate gratuite cu posibilitatea de actualizare automată. Pentru a folosi serviciul, aveți nevoie de pachetul autocert.
Cel mai simplu mod de a-l configura este să folosiți metoda autocert.NewListener în combinație cu http.Serve. Această metodă permite obținerea și actualizarea certificatelor TLS, în timp ce serverul HTTP procesează cererile:
http.Serve(autocert.NewListener("example.com"), nil)Dacă deschidem în browser , atunci primim un răspuns HTTPS „Hello, world!”.
Dacă aveți nevoie de o configurare mai detaliată, ar trebui să folosiți managerul autocert.Manager. Apoi, creăm o instanță personalizată http.Server (până acum am folosit instanța implicită) și adăugăm managerul în serverul 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("", "")Aceasta este o modalitate simplă de a implementa suport complet pentru HTTPS cu actualizarea automată a certificatului.
Adăugarea de rute personalizate
Routerul implicit inclus în biblioteca standard este bun, dar foarte simplu. În majoritatea aplicațiilor, este necesară o rutare mai complexă, inclusiv rute încorporate și wildcards, sau o procedură de configurare a șabloanelor și parametrilor căii.
În acest caz, ar trebui să folosim pachete și . Cu acesta vom învăța să lucrăm — mai jos este un exemplu.
Dat fiind — fișierul api/v1/api.go, care conține rutele pentru API-ul nostru:
/ 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
}Setăm un prefix api/vq pentru rutele în fișierul principal.
Putem apoi să îl montăm pe routerul nostru principal sub prefixul api/v1 în aplicația noastră principală:
// 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())Ușurința de lucru cu rutele complexe în Go face posibilă simplificarea structurării managementului aplicațiilor mari și complexe.
Lucrul cu middleware
În cazul prelucrării intermediare, se folosește învăluirea unui handler HTTP în altul, ceea ce permite rapid efectarea autentificării, comprimirii, logării și a altor funcții.
Ca exemplu, să luăm interfața http.Handler, cu ajutorul căreia vom scrie un handler cu autentificarea utilizatorilor serviciului.
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
}
// Presupunând că autentificarea a trecut, rulează handler-ul original
next.ServeHTTP(w, r)
})
}Există routere externe, de exemplu, chi, care permit extinderea funcționalității prelucrării intermediare.
Lucrul cu fișiere statice
Biblioteca standard Go include funcționalități pentru lucrul cu conținut static, inclusiv imagini, precum și fișiere JavaScript și CSS. Accesul la acestea se poate realiza prin funcția http.FileServer. Aceasta returnează un handler care distribuie fișiere dintr-un anumit director.
func NewRouter() http.Handler {
router := chi.NewRouter()
r.Get("/{name}", HelloName)
// Configurarea distribuitorului de fișiere statice
staticPath, _ := filepath.Abs("..../../static/")
fs := http.FileServer(http.Dir(staticPath))
router.Handle("/*", fs)
return rEste important să reținem că http.Dir va afișa conținutul unui director doar dacă nu există un fișier principal index.html. În acest caz, pentru a preveni compromiterea directorului, este recomandat să folosiți pachetul .
Oprire corectă
În Go există și o funcție pentru oprirea corectă a serverului HTTP. Acest lucru se poate face cu ajutorul metodei Shutdown(). Serverul pornește într-o goroutine, iar apoi canalul este supravegheat pentru a primi un semnal de întrerupere. Odată ce semnalul este primit, serverul se oprește, dar nu imediat, ci după câteva secunde.
handler := server.NewRouter()
srv := &http.Server{
Handler: handler,
}
go func() {
srv.Serve(autocert.NewListener(domains...))
}()
// Așteptați o întrerupere
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
// Încercare de oprire corectă
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(ctx)În concluzie
Go este un limbaj puternic cu o bibliotecă standard practic universală. Capacitățile sale implicite sunt foarte variate, iar acestea pot fi îmbunătățite prin utilizarea interfețelor — ceea ce permite dezvoltarea serverelor HTTP cu adevărat de încredere.
Skillbox recomandă:
- Curs practic de doi ani .
- Curs online educațional .
- Curs practic anual .
Sursa: habr.com
