Dezvoltare de server web în Golang - de la simplu la complex

Dezvoltare de server web în Golang - de la simplu la complex

Acum cinci ani am început dezvolta Gophish, aceasta a oferit ocazia de a învăța Golang. Mi-am dat seama că Go este un limbaj puternic, completat de multe biblioteci. Go este versatil: în special, poate fi folosit pentru a dezvolta aplicații pe partea de server fără probleme.

Acest articol este despre scrierea unui server în Go. Să începem cu lucruri simple, cum ar fi „Bună lume!” și să terminăm cu o aplicație cu următoarele capabilități:

- Utilizarea Let's Encrypt pentru HTTPS.
— Lucrează ca router API.
— Lucrul cu middleware.
— Procesarea fișierelor statice.
— Oprire corectă.

Skillbox recomandă: Curs practic „Dezvoltator Python de la zero”.

Amintim: pentru toți cititorii „Habr” - o reducere de 10 de ruble la înscrierea la orice curs Skillbox folosind codul promoțional „Habr”.

Salut Lume!

Puteți crea un server web în Go foarte rapid. Iată un exemplu de utilizare a unui handler care returnează „Hello, world!” promis mai sus.

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 localhost, apoi veți vedea imediat textul „Bună, lume!” (dacă totul funcționează corect, desigur).

Vom folosi handlerul de mai multe ori mai târziu, dar mai întâi să înțelegem cum funcționează totul.

net/http

Exemplul a folosit pachetul net/http, este instrumentul principal din Go pentru dezvoltarea atât a serverelor, cât și a clienților HTTP. Pentru a înțelege codul, să înțelegem semnificația a trei elemente importante: http.Handler, http.ServeMux și http.Server.

handlere HTTP

Când primim o solicitare, handlerul o analizează și generează un răspuns. Handler-urile din Go sunt implementate după cum urmează:

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

Primul exemplu folosește funcția de ajutor http.HandleFunc. Acesta include o altă funcție, care, la rândul său, preia http.ResponseWriter și http.Request în ServeHTTP.

Cu alte cuvinte, handlerele din Golang sunt prezentate într-o singură interfață, care oferă o mulțime de opțiuni programatorului. Deci, de exemplu, middleware-ul este implementat folosind un handler, unde ServeHTTP mai întâi face ceva și apoi apelează metoda ServeHTTP a altui handler.

După cum sa menționat mai sus, handlerii pur și simplu generează răspunsuri la solicitări. Dar ce anume handler ar trebui utilizat la un anumit moment în timp?

Solicitați rutare

Pentru a face alegerea corectă, utilizați un multiplexor HTTP. Într-un număr de biblioteci se numește muxer sau router, dar toate sunt același lucru. Funcția multiplexorului este de a analiza calea cererii și de a selecta handlerul corespunzător.

Dacă aveți nevoie de suport pentru rutarea complexă, atunci este mai bine să utilizați biblioteci terțe. Unele dintre cele mai avansate - gorilă/mux и go-chi/chi, aceste biblioteci fac posibilă implementarea fără probleme a procesării intermediare. Cu ajutorul lor, puteți configura rutarea wildcard și puteți efectua o serie de alte sarcini. Avantajul lor este compatibilitatea cu handlerele HTTP standard. Ca rezultat, puteți scrie cod simplu care poate fi modificat în viitor.

Lucrul cu cadre complexe într-o situație normală va necesita soluții non-standard, iar acest lucru complică semnificativ utilizarea handlerelor implicite. Pentru a crea marea majoritate a aplicațiilor, va fi suficientă o combinație între biblioteca implicită și un router simplu.

Procesarea interogărilor

În plus, avem nevoie de o componentă care să „asculte” conexiunile de intrare și să redirecționeze toate cererile către handlerul corect. http.Server poate gestiona cu ușurință această sarcină.

Următoarele arată că serverul este responsabil pentru toate sarcinile care sunt legate de procesarea conexiunii. Acesta, de exemplu, funcționează folosind protocolul TLS. Pentru a implementa apelul http.ListenAndServer, este utilizat un server HTTP standard.

Acum să ne uităm la exemple mai complexe.

Adăugarea Let's Encrypt

În mod implicit, aplicația noastră rulează prin protocolul HTTP, dar se recomandă utilizarea protocolului HTTPS. Acest lucru se poate face fără probleme în Go. Dacă ați primit un certificat și o cheie privată, atunci este suficient să înregistrați ListenAndServeTLS cu certificatul și fișierele cheie corecte.

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

Întotdeauna poți face mai bine.

Să ștergem oferă certificate gratuite cu reînnoire automată. Pentru a utiliza serviciul, aveți nevoie de un pachet autocert.

Cel mai simplu mod de a-l configura este să folosești metoda autocert.NewListener în combinație cu http.Serve. Metoda vă permite să obțineți și să actualizați certificate TLS în timp ce serverul HTTP procesează solicitările:

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

Dacă deschidem în browser example.com, vom primi un răspuns HTTPS „Bună ziua, lume!”

Dacă aveți nevoie de o configurare mai detaliată, atunci ar trebui să utilizați managerul autocert.Manager. Apoi creăm propria noastră instanță http.Server (până acum o folosim implicit) și adăugăm managerul la 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 HTTPS cu reînnoirea automată a certificatului.

Adăugarea de rute personalizate

Routerul implicit inclus în biblioteca standard este bun, dar este foarte simplu. Majoritatea aplicațiilor necesită o rutare mai complexă, inclusiv rute imbricate și wildcard, sau o procedură pentru setarea modelelor și parametrilor de cale.

În acest caz, merită să folosiți pachete gorilă/mux и go-chi/chi. Vom învăța cum să lucrăm cu acesta din urmă - un exemplu este prezentat mai jos.

Este dat fișierul api/v1/api.go care conține rute 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 prefixul api/vq pentru rute în fișierul principal.

Apoi îl putem monta pe routerul nostru principal sub prefixul api/v1/ înapoi î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 Go de a lucra cu rute complexe face posibilă simplificarea structurii și întreținerii aplicațiilor mari și complexe.

Lucrul cu middleware

Staging implică împachetarea unui handler HTTP cu altul, făcând posibilă efectuarea rapidă de autentificare, compresie, înregistrare în jurnal și alte câteva funcții.

De exemplu, să ne uităm la interfața http.Handler; o vom folosi pentru a scrie un handler care autentifică utilizatorii 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
        }
        // Assuming authentication passed, run the original handler
        next.ServeHTTP(w, r)
    })
}

Există routere terțe, cum ar fi chi, care vă permit să extindeți funcționalitatea middleware.

Lucrul cu fișiere statice

Biblioteca standard Go include capabilități de lucru cu conținut static, inclusiv imagini, fișiere JavaScript și CSS. Acestea pot fi accesate prin intermediul funcției http.FileServer. Returnează un handler care servește fișiere dintr-un anumit director.

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

Cu siguranță merită să ne amintim că http.Dir afișează conținutul directorului dacă nu conține fișierul principal index.html. În acest caz, pentru a preveni compromiterea directorului, ar trebui să utilizați pachetul unindexed.

Oprire corectă

Go are și o funcție numită închidere grațioasă a serverului HTTP. Acest lucru se poate face folosind metoda Shutdown(). Serverul este pornit într-o rutină, apoi canalul este ascultat pentru a primi un semnal de întrerupere. Imediat 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...))
}()
 
// 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)

Ca o concluzie

Go este un limbaj puternic, cu o bibliotecă standard aproape universală. Capacitățile sale implicite sunt foarte largi și pot fi îmbunătățite folosind interfețe - acest lucru vă permite să dezvoltați servere HTTP cu adevărat fiabile.

Skillbox recomandă:

Sursa: www.habr.com

Adauga un comentariu