
Acum cinci ani, am început , ceea ce mi-a permis să învăț Golang. Am realizat că Go este un limbaj puternic, al cărui potențial este completat de numeroase biblioteci. Go este versatil: în special, cu ajutorul său, este ușor să dezvolți aplicații server.
Acest articol este dedicat scrierii unui server în Go. Vom începe cu lucruri simple, cum ar fi „Hello world!”, și vom termina cu o aplicație cu următoarele funcționalități:
— Utilizarea Let’s Encrypt pentru HTTPS.
— Funcționarea ca API router.
— Lucrul cu middleware.
— Gestionarea fișierelor statice.
— Încheiere corectă.
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 se poate face foarte repede. Iată un exemplu de utilizare a unui handler care returnează celebrul „Hello, world!”.
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ă rulați aplicația și deschideți pagina , veți vedea imediat textul „Hello, world!” (desigur, dacă totul funcționează corect).
Apoi, vom folosi repetat handler-ul, dar mai întâi haideți să înțelegem cum funcționează totul.
net/http
În exemplu a fost folosit pachetul , care este principalul instrument în Go pentru dezvoltarea atât a serverelor, cât și a clienților HTTP. Pentru a înțelege codul, să analizăm semnificația a trei elemente importante: http.Handler, http.ServeMux și http.Server.
Handler-ele HTTP
Când primim o cerere, handler-ul o analizează și generează un răspuns. Handler-ele în Go sunt implementate astfel:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}În primul exemplu se folosește funcția utilitară http.HandleFunc. Aceasta învăluie o altă funcție, care, la rândul său, primește http.ResponseWriter și http.Request în ServeHTTP.
Cu alte cuvinte, handler-ele în Golang sunt reprezentate printr-un singur interfață, ceea ce oferă numeroase oportunități pentru programatori. De exemplu, middleware-ul este implementat printr-un handler, unde ServeHTTP inițial face ceva și apoi apelează metoda ServeHTTP a altui handler.
Așa cum s-a menționat mai sus, handler-ele doar generează răspunsuri la cereri. Dar ce handler specific trebuie utilizat în fiecare moment?
Rutarea cererilor
Pentru a face o alegere corectă, utilizați multiplexorul HTTP. În unele biblioteci, acesta este denumit muxer sau router, însă toate acestea se referă la același lucru. Funcția multiplexorului constă în analizarea căii cererii și alegerea handler-ului corespunzător.
Dacă aveți nevoie de suport pentru rutare complexă, atunci este mai bine să folosiți biblioteci externe. Unele dintre cele mai avansate sunt și , aceste biblioteci oferă posibilitatea de a implementa tratarea intermediară fără probleme semnificative. Cu ajutorul lor, puteți configura rutare wildcard și realiza o serie de alte sarcini. Avantajul lor este compatibilitatea cu handler-ele HTTP standard. Ca rezultat, puteți scrie cod simplu cu posibilitatea de a-l modifica în viitor.
Lucrul cu framework-uri complexe în situații obișnuite va necesita soluții oarecum neobișnuite, ceea ce complică semnificativ utilizarea handler-elor implicite. Pentru crearea celor mai multe aplicații, combinația dintre biblioteca implicită și un router simplu este suficientă.
Tratarea cererilor
În plus, avem nevoie de un component care să „asculte” conexiunile intrante și să redirecționeze toate cererile către handler-ul corect. Acest lucru poate fi realizat cu ușurință prin http.Server.
Mai jos este arătat că serverul este responsabil pentru toate sarcinile legate de tratarea conexiunilor. Acestea includ, de exemplu, lucrul cu protocolul TLS. Pentru a implementa apelul http.ListenAndServer, se folosește serverul HTTP standard.
Acum haideți să analizăm exemple mai complexe.
Adăugarea Let’s Encrypt
În mod implicit, aplicația noastră funcționează pe protocolul HTTP, însă se recomandă utilizarea protocolului HTTPS. În Go, acest lucru se poate face fără probleme. Dacă ați obținut un certificat și o cheie privată, atunci este suficient să specificați ListenAndServeTLS cu indicarea fișierelor corecte pentru certificat și cheie.
http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil)Întotdeauna se poate face mai bine.
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. Metoda 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 vom obține 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 propria instanță http.Server (până acum am folosit-o pe cea implicită) și adăugăm managerul în server 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 actualizare 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 imbricate și wildcard sau o procedură de configurare a modelului și a parametrilor de cale.
În acest caz, ar trebui să folosiți pachetele și . Cu acesta vom învăța să lucrăm — mai jos este un exemplu.
Avem un fișier 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 pentru rutele api/vq în fișierul principal.
Putem atunci să le montăm pe routerul nostru principal sub prefixul api/v1/ în aplicația 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())Simplitatea lucrului cu rutele complexe în Go permite o structurare mai ușoară în întreținerea aplicațiilor mari și complexe.
Lucrul cu middleware
În cazul procesării intermediare, este folosită învelirea unui handler HTTP de către altul, ceea ce permite o autentificare rapidă, comprimare, jurnalizare și unele alte 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 fost aprobată, rulați handlerul original
next.ServeHTTP(w, r)
})
}Există routere terțe, cum ar fi chi, care permit extinderea funcționalității procesării intermediare.
Lucrul cu fișiere statice
Biblioteca standard Go include funcționalități pentru a gestiona conținutul static, inclusiv imagini, precum și fișiere JavaScript și CSS. Accesul la acestea se poate face prin funcția http.FileServer. Aceasta returnează un handler care servește fișiere dintr-un director specific.
func NewRouter() http.Handler {
router := chi.NewRouter()
r.Get("/{name}", HelloName)
// Configurarea distribuirii fișierelor statice
staticPath, _ := filepath.Abs("../../static/")
fs := http.FileServer(http.Dir(staticPath))
router.Handle("/*", fs)
return rEste important să reții că http.Dir va afișa conținutul unui director doar dacă nu există un fișier index.html principal în acesta. În acest caz, pentru a evita compromiterea directorului, ar trebui să folosești pachetul .
Închidere corectă
În Go, există și o funcție pentru închiderea corectă a serverului HTTP. Acest lucru se poate face folosind metoda Shutdown(). Serverul pornește într-o gorutină, iar apoi se ascultă un canal pentru a primi semnale 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șteaptă o întrerupere
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
// Încearcă o oprire grațioasă
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ă. Capabilitățile sale implicite sunt destul de vaste, iar acestea pot fi îmbunătățite prin intermediul interfețelor — ceea ce permite dezvoltarea de servere HTTP cu adevărat fiabile.
Skillbox recomandă:
- Curs practic de doi ani .
- Curs online educațional .
- Curs practic anual .
Sursa: habr.com
