Pag-unlad ng web server sa Golang - mula simple hanggang kumplikado

Pag-unlad ng web server sa Golang - mula simple hanggang kumplikado

Limang taon na ang nakalipas nagsimula ako bumuo ng Gophish, nagbigay ito ng pagkakataong matuto ng Golang. Napagtanto ko na ang Go ay isang makapangyarihang wika, na kinukumpleto ng maraming aklatan. Ang Go ay maraming nalalaman: sa partikular, maaari itong magamit upang bumuo ng mga application sa panig ng server nang walang anumang mga problema.

Ang artikulong ito ay tungkol sa pagsulat ng isang server sa Go. Magsimula tayo sa mga simpleng bagay tulad ng "Hello world!" at magtapos sa isang application na may mga sumusunod na kakayahan:

- Paggamit ng Let's Encrypt para sa HTTPS.
— Nagtatrabaho bilang isang API router.
— Paggawa gamit ang middleware.
— Pagproseso ng mga static na file.
— Tamang pagsara.

Inirerekomenda ng Skillbox ang: Praktikal na kurso "Python developer mula sa simula".

Pinapaalala namin sa iyo: para sa lahat ng mga mambabasa ng "Habr" - isang diskwento na 10 rubles kapag nag-enroll sa anumang kurso sa Skillbox gamit ang code na pang-promosyon ng "Habr".

Kumusta, mundo!

Maaari kang lumikha ng isang web server sa Go nang napakabilis. Narito ang isang halimbawa ng paggamit ng handler na nagbabalik ng "Hello, world!" na ipinangako sa itaas.

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)
}

Pagkatapos nito, kung patakbuhin mo ang application at buksan ang pahina localhost, pagkatapos ay makikita mo kaagad ang tekstong “Hello, world!” (kung ang lahat ay gumagana nang tama, siyempre).

Gagamitin namin ang handler nang maraming beses sa ibang pagkakataon, ngunit unahin muna natin kung paano gumagana ang lahat.

net/http

Ang halimbawa ay ginamit ang pakete net/http, ito ang pangunahing tool sa Go para sa pagbuo ng parehong mga server at HTTP client. Upang maunawaan ang code, unawain natin ang kahulugan ng tatlong mahahalagang elemento: http.Handler, http.ServeMux at http.Server.

Mga humahawak ng HTTP

Kapag nakatanggap kami ng kahilingan, sinusuri ito ng handler at bubuo ng tugon. Ang mga Handler sa Go ay ipinapatupad tulad ng sumusunod:

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

Ang unang halimbawa ay gumagamit ng http.HandleFunc helper function. Binabalot nito ang isa pang function, na kung saan ay tumatagal ng http.ResponseWriter at http.Request sa ServeHTTP.

Sa madaling salita, ang mga humahawak sa Golang ay ipinakita sa isang solong interface, na nagbibigay ng maraming mga pagpipilian sa programmer. Kaya, halimbawa, ang middleware ay ipinatupad gamit ang isang handler, kung saan ang ServeHTTP ay unang gumawa ng isang bagay at pagkatapos ay tinawag ang ServeHTTP na paraan ng isa pang handler.

Gaya ng nabanggit sa itaas, ang mga humahawak ay gumagawa lang ng mga tugon sa mga kahilingan. Ngunit aling partikular na tagapangasiwa ang dapat gamitin sa isang partikular na punto ng oras?

Humiling ng Pagruruta

Upang makagawa ng tamang pagpili, gumamit ng HTTP multiplexer. Sa isang bilang ng mga aklatan ito ay tinatawag na muxer o router, ngunit ang mga ito ay lahat ng parehong bagay. Ang function ng multiplexer ay pag-aralan ang path ng kahilingan at piliin ang naaangkop na handler.

Kung kailangan mo ng suporta para sa kumplikadong pagruruta, mas mainam na gumamit ng mga aklatan ng third-party. Ilan sa mga pinaka-advanced na - bakulaw/mux и go-chi/chi, ginagawang posible ng mga aklatang ito na ipatupad ang intermediate processing nang walang anumang problema. Sa tulong nila, maaari mong i-configure ang wildcard na pagruruta at magsagawa ng ilang iba pang gawain. Ang kanilang kalamangan ay ang pagiging tugma sa karaniwang mga humahawak ng HTTP. Bilang resulta, maaari kang magsulat ng simpleng code na maaaring baguhin sa hinaharap.

Ang pagtatrabaho sa mga kumplikadong framework sa isang normal na sitwasyon ay mangangailangan ng hindi karaniwang mga solusyon, at ito ay makabuluhang nagpapalubha sa paggamit ng mga default na humahawak. Upang lumikha ng karamihan ng mga application, isang kumbinasyon ng default na library at isang simpleng router ay sapat na.

Pagproseso ng Query

Bilang karagdagan, kailangan namin ng isang bahagi na "makikinig" para sa mga papasok na koneksyon at ire-redirect ang lahat ng mga kahilingan sa tamang handler. Madaling mahawakan ng http.Server ang gawaing ito.

Ang sumusunod ay nagpapakita na ang server ay responsable para sa lahat ng mga gawain na nauugnay sa pagpoproseso ng koneksyon. Ito, halimbawa, ay gumagana gamit ang TLS protocol. Upang ipatupad ang http.ListenAndServer na tawag, isang karaniwang HTTP server ang ginagamit.

Ngayon tingnan natin ang mas kumplikadong mga halimbawa.

Pagdaragdag ng Let's Encrypt

Bilang default, tumatakbo ang aming application sa HTTP protocol, ngunit inirerekomendang gamitin ang HTTPS protocol. Magagawa ito nang walang problema sa Go. Kung nakatanggap ka ng sertipiko at pribadong key, sapat na upang irehistro ang ListenAndServeTLS gamit ang tamang certificate at mga key file.

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

Lagi kang makakagawa ng mas mahusay.

Mag-encrypt tayo nagbibigay ng mga libreng sertipiko na may awtomatikong pag-renew. Upang magamit ang serbisyo, kailangan mo ng isang pakete autocert.

Ang pinakamadaling paraan upang i-configure ito ay ang paggamit ng autocert.NewListener na pamamaraan kasama ng http.Serve. Ang pamamaraan ay nagbibigay-daan sa iyo na makakuha at mag-update ng mga TLS certificate habang ang HTTP server ay nagpoproseso ng mga kahilingan:

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

Kung bubuksan natin sa browser example.com, makakatanggap kami ng HTTPS na tugon na “Hello, world!”

Kung kailangan mo ng mas detalyadong configuration, dapat mong gamitin ang autocert.Manager manager. Pagkatapos ay gumawa kami ng sarili naming http.Server instance (hanggang ngayon ginamit namin ito bilang default) at idagdag ang manager sa TLSConfig server:

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("", "")

Ito ay isang madaling paraan upang ipatupad ang buong suporta sa HTTPS na may awtomatikong pag-renew ng certificate.

Pagdaragdag ng mga custom na ruta

Ang default na router na kasama sa karaniwang library ay mabuti, ngunit ito ay napaka-basic. Karamihan sa mga application ay nangangailangan ng mas kumplikadong pagruruta, kabilang ang mga nested at wildcard na ruta, o isang pamamaraan para sa pagtatakda ng mga pattern at parameter ng path.

Sa kasong ito ito ay nagkakahalaga ng paggamit ng mga pakete bakulaw/mux и go-chi/chi. Matututunan natin kung paano magtrabaho kasama ang huli - isang halimbawa ang ipinapakita sa ibaba.

Ang ibinigay ay ang file na api/v1/api.go na naglalaman ng mga ruta para sa aming 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
}

Itinakda namin ang api/vq prefix para sa mga ruta sa pangunahing file.

Maaari naming i-mount ito sa aming pangunahing router sa ilalim ng api/v1/ prefix pabalik sa aming pangunahing application:

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

Ang kadalian ng pagtatrabaho ni Go sa mga kumplikadong ruta ay ginagawang posible na pasimplehin ang pag-istruktura at pagpapanatili ng malaki, kumplikadong mga aplikasyon.

Nagtatrabaho sa middleware

Kasama sa staging ang pagbabalot ng isang HTTP handler sa isa pa, na ginagawang posible upang mabilis na maisagawa ang pagpapatunay, compression, pag-log, at ilang iba pang mga function.

Bilang halimbawa, tingnan natin ang interface ng http.Handler; gagamitin natin ito upang magsulat ng handler na nagpapatotoo sa mga gumagamit ng serbisyo.

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)
    })
}

May mga third party na router, gaya ng chi, na nagbibigay-daan sa iyong palawigin ang middleware functionality.

Paggawa gamit ang mga static na file

Kasama sa Go standard library ang mga kakayahan para sa pagtatrabaho sa static na content, kabilang ang mga larawan, JavaScript at CSS file. Maaari silang ma-access sa pamamagitan ng http.FileServer function. Nagbabalik ito ng handler na nagsisilbi ng mga file mula sa isang partikular na direktoryo.

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

Talagang nararapat tandaan na ang http.Dir ay nagpapakita ng mga nilalaman ng direktoryo kung hindi ito naglalaman ng pangunahing index.html file. Sa kasong ito, upang maiwasang makompromiso ang direktoryo, dapat mong gamitin ang package unindexed.

Tamang shutdown

Ang Go ay mayroon ding tampok na tinatawag na magandang pagsara ng HTTP server. Magagawa ito gamit ang pamamaraan ng Shutdown(). Ang server ay sinimulan sa isang goroutine, at pagkatapos ay ang channel ay pinakinggan upang makatanggap ng isang interrupt na signal. Sa sandaling matanggap ang signal, ang server ay naka-off, ngunit hindi kaagad, ngunit pagkatapos ng ilang segundo.

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)

Bilang isang konklusyon

Ang Go ay isang malakas na wika na may halos unibersal na karaniwang library. Ang mga default na kakayahan nito ay napakalawak, at mapapahusay ang mga ito gamit ang mga interface - nagbibigay-daan ito sa iyo na bumuo ng tunay na maaasahang mga HTTP server.

Inirerekomenda ng Skillbox ang:

Pinagmulan: www.habr.com

Magdagdag ng komento