Razvoj web poslužitelja na Golangu — od jednostavnog do složenog

Razvoj web poslužitelja na Golangu — od jednostavnog do složenog

Prije pet godina počeo sam razvijati Gophish, to mi je omogućilo da učim Golang. Shvatio sam da je Go moćan jezik čije mogućnosti dopunjuju brojni alati. Go je univerzalan: posebno, uz njegovu pomoć lako je razvijati serverske aplikacije.

Ovaj članak posvećen je pisanju servera na Go-u. Počnimo s jednostavnim stvarima, kao što je "Hello world!", a završimo aplikacijom koja ima sljedeće mogućnosti:

— Korištenje Let’s Encrypt za HTTPS.
— Rad kao API rutera.
— Rad s middleware-om.
— Obrada statičnih datoteka.
— Ispravno zatvaranje rada.

Skillbox preporučuje: Praktični tečaj "Python programer od nule".

Podsjećamo: za sve čitatelje "Habra" — popust od 10 000 rubalja prilikom upisa na bilo koji tečaj Skillbox uz promo kod "Habr".

Hello, world!

Brzo možete stvoriti web server na Go-u. Evo primjera korištenja handlera koji vraća ranije spomenuti "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)
}

Nakon toga, ako pokrenete aplikaciju i otvorite stranicu localhost, odmah ćete vidjeti tekst "Hello, world!" (naravno, ako sve ispravno radi).

Zatim ćemo više puta koristiti handler, ali prvo hajde da shvatimo kako sve to funkcioniše.

net/http

U primjeru se koristi paket net/http, to je osnovno sredstvo u Go-u za razvoj i servera i HTTP klijenata. Da bismo razumjeli kod, razjasnimo značenje tri važna elementa: http.Handler, http.ServeMux i http.Server.

HTTP handleri

Kada primimo zahtjev, handler ga analizira i formira odgovor. Handleri u Go-u su implementirani na sljedeći način:

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

U prvom primjeru koristi se pomoćna funkcija http.HandleFunc. Ona omotava drugu funkciju koja, zauzvrat, prima http.ResponseWriter i http.Request u ServeHTTP.

Drugim riječima, handleri u Golangu predstavljeni su jedinstvenim interfejsom, što programerima pruža brojne mogućnosti. Na primjer, middleware je implementiran putem handlera, gdje ServeHTTP prvo radi nešto, a zatim poziva metodu ServeHTTP drugog handlera.

Kao što je obenuto, handleri jednostavno formiraju odgovore na zahtjeve. No, koji točno handler treba koristiti u određenom trenutku?

Ruting zahtjeva

Kako biste donijeli pravu odluku, koristite HTTP multiplexor. U nekim bibliotekama nazivaju ga muxer ili router, no sve je to isto. Funkcija multiplexora je analizirati putanju zahtjeva i odabrati odgovarajući obrađivač.

Ako je potrebna podrška za složeno usmjeravanje, bolje je koristiti vanjske biblioteke. Neke od najnaprednijih su gorilla/mux i go-chi/chi, ove biblioteke omogućuju realizaciju međuprocesne obrade bez posebnih problema. Uz njihovu pomoć možete postaviti wildcard usmjeravanje i izvršiti niz drugih zadataka. Njihova prednost je kompatibilnost sa standardnim HTTP obrađivačima. Kao rezultat toga, možete napisati jednostavan kod s mogućnošću njegove modifikacije u budućnosti.

Rad sa složenim okvirima u običnim situacijama zahtijevat će ne posve standardna rješenja, što značajno otežava korištenje zadnjih obrađivača. Za izradu velike većine aplikacija dovoljna je kombinacija zadane biblioteke i jednostavnog usmjerivača.

Obrada zahtjeva

Osim toga, potrebna nam je komponenta koja će "slušati" dolazne veze i preusmjeravati sve zahtjeve pravom obrađivaču. Ovaj zadatak bez poteškoća će obaviti http.Server.

Ispod je prikazano da server odgovara za sve zadatke koji se odnose na obradu veza. To uključuje rad s TLS protokolom. Za realizaciju poziva http.ListenAndServe koristi se standardni HTTP server.

Sada pogledajmo složenije primjere.

Dodavanje Let’s Encrypt

Prema zadanim postavkama, naša aplikacija radi putem HTTP protokola, međutim preporučuje se koristiti HTTPS protokol. U Go-u se to može lako učiniti. Ako ste dobili certifikat i privatni ključ, dovoljnu je navesti ListenAndServeTLS s pravilnim datotekama certifikata i ključa.

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

Uvijek se može bolje.

Let’s Encrypt nudi besplatne certifikate s mogućnošću automatskog obnavljanja. Da biste koristili uslugu, potreban je paket autocert.

Najlakši način da ga postavite je koristiti metodu autocert.NewListener u kombinaciji s http.Serve. Metoda omogućuje dobivanje i obnavljanje TLS certifikata, dok HTTP server obrađuje zahtjeve:

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

Ako otvorimo u pregledniku example.com, dobijamo HTTPS odgovor „Hello, world!“.

Ako je potrebna detaljnija konfiguracija, vrijedi koristiti autocert.Manager. Zatim kreiramo vlastiti instancu http.Server (do sada smo koristili zadanu) i dodajemo menadžer u 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("", "")

Ovo je jednostavan način za implementaciju potpune podrške za HTTPS s automatskim obnavljanjem certifikata.

Dodavanje nestandardnih ruta

Zadani usmjerivač uključen u standardnu biblioteku je dobar, ali je vrlo jednostavan. U većini aplikacija potrebna je složenija usmjeravanje, uključujući ugniježdene i wildcard rute ili postupak postavljanja predložaka i parametara putanje.

U ovom slučaju vrijedi koristiti pakete gorilla/mux i go-chi/chi. S posljednjim ćemo naučiti raditi — u nastavku je prikazan primjer.

Imamo — datoteku api/v1/api.go, koja sadrži rute za naš 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
}

Postavljamo prefiks za rute api/vq u glavnoj datoteci.

Tada to možemo montirati na naš glavni usmjerivač pod prefiksom api/v1 u našoj glavnoj aplikaciji:

// 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())

Jednostavnost rada sa složenim rutama u Go omogućava pojednostavljenje strukture održavanja velikih kompleksnih aplikacija.

Rad s middleware-om

U slučaju međuprosječivanja koristi se omotač jednog HTTP obrađivača drugim, što omogućava brzo provoditi autentifikaciju, kompresiju, logiranje i neke druge funkcije.

Kao primjer razmotrit ćemo sučelje http.Handler, pomoću kojeg ćemo napisati obrađivač s autentifikacijom korisnika servisa.

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
        }
        // Pretpostavljajući da je autentifikacija prošla, pokreni izvorni obrađivač
        next.ServeHTTP(w, r)
    })
}

Postoje i vanjski usmjerivači, poput chi, koji omogućuju proširenje funkcionalnosti međuprosječivanja.

Rad sa statičkim datotekama

Standardna biblioteka Go sadrži mogućnosti za rad sa statičkim sadržajem, uključujući slike, kao i JavaScript i CSS datoteke. Pristup njima može se dobiti putem funkcije http.FileServer. Ona vraća obrađivač koji isporučuje datoteke iz određenog direktorija.

func NewRouter() http.Handler {
    router := chi.NewRouter()
    r.Get("/{name}", HelloName)
 
// Konfiguracija dijeljenja statičkih datoteka
staticPath, _ := filepath.Abs("..\/..\/static\/\")
fs := http.FileServer(http.Dir(staticPath))
    router.Handle("/*", fs)
    
    return r

Obavezno treba imati na umu da http.Dir prikazuje sadržaj direktorija u slučaju da nema glavne datoteke index.html. U tom slučaju, kako bi se spriječila kompromitacija direktorija, treba koristiti paket neindeksirano.

Ispravno zatvaranje rada

U Go-u postoji i funkcija za ispravno zatvaranje HTTP poslužitelja. To se može učiniti pomoću metode Shutdown(). Poslužitelj se pokreće u gorutini, a zatim se kanal nadgleda za primanje signala prekida. Čim signal bude primljen, poslužitelj se isključuje, ali ne odmah, već nakon nekoliko sekundi.

handler := server.NewRouter()
srv := &http.Server{
    Handler: handler,
}
 
go func() {
srv.Serve(autocert.NewListener(domains...))
}()
 
// Čekanje na prekid
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
 
// Pokušaj ispravnog zatvaranja
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
def defer cancel()
srv.Shutdown(ctx)

Kao zaključak

Go je moćan jezik s praktički univerzalnom standardnom bibliotekom. Njegove zadane mogućnosti su vrlo široke, a mogu se poboljšati pomoću sučelja — to omogućuje razvoj doista pouzdanih HTTP poslužitelja.

Skillbox preporučuje:

Izvor: habr.com

Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere 🔥 Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere | ProHoster