Распрацоўка вэб-сервераў на Golang – ад простага да складанага

Распрацоўка вэб-сервераў на Golang – ад простага да складанага

Пяць гадоў таму я пачаў распрацоўваць Gophish, гэта дало магчымасць вывучыць Golang. Я зразумеў, што Go – магутная мова, магчымасці якой дапаўняюцца мноствам бібліятэк. Go універсальны: у прыватнасці, з яго дапамогай можна без праблем распрацоўваць серверныя прыкладанні.

Гэты артыкул прысвечаны напісанню сервера на Go. Пачнём з простых рэчаў, накшталт «Hello world!», а скончым дадаткам з такімі магчымасцямі:

- Выкарыстанне Let's Encrypt для HTTPS.
- Праца ў якасці API-маршрутызатара.
- Праца з middleware.
- Апрацоўка статычных файлаў.
- Карэктнае завяршэнне працы.

Skillbox рэкамендуе: Практычны курс "Python-распрацоўшчык з нуля".

Нагадваем: для ўсіх чытачоў "Хабра" - зніжка 10 000 рублёў пры запісе на любы курс Skillbox па промакодзе "Хабр".

Прывітанне Сусвет!

Стварыць вэб-сервер на Go можна вельмі хутка. Вось прыклад выкарыстання апрацоўшчыка, які вяртае абяцаны вышэй "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)
}

Пасля гэтага, калі запусціць прыкладанне і адкрыць старонку лакальны, то вы адразу ўбачыце тэкст "Hello, world!" (вядома, калі ўсё працуе правільна).

Далей мы будзем неаднаразова выкарыстоўваць апрацоўшчык, але спачатку давайце зразумеем, як усё ўладкована.

net/http

У прыкладзе выкарыстоўваўся пакет net/http, гэта асноўны сродак у Go для распрацоўкі як сервераў, так і HTTP-кліентаў. Для таго, каб зразумець код, давайце разбяромся ў значэнні трох важных элементаў: http.Handler, http.ServeMux і http.Server.

HTTP-апрацоўшчыкі

Калі мы атрымліваем запыт, апрацоўшчык аналізуе яго і фармуе адказ. Апрацоўшчыкі ў Go рэалізаваны наступным чынам:

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

У першым прыкладзе выкарыстоўваецца дапаможная функцыя http.HandleFunc. Яна абгортвае іншую функцыю, якая, у сваю чаргу, прымае http.ResponseWriter і http.Request у ServeHTTP.

Іншымі словамі, апрацоўшчыкі ў Golang прадстаўлены адзіным інтэрфейсам, што дае мноства магчымасцяў для праграміста. Так, напрыклад, middleware рэалізавана пры дапамозе апрацоўшчыка, дзе ServeHTTP спачатку нешта робіць, а затым выклікае метад ServeHTTP іншага апрацоўшчыка.

Як і гаварылася вышэй, апрацоўшчыкі проста фармуюць адказы на запыты. Але які менавіта апрацоўшчык трэба выкарыстоўваць у пэўны момант часу?

Маршрутызацыя запытаў

Для таго, каб зрабіць правільны выбар, скарыстайцеся HTTP-мультыплексарам. У шэрагу бібліятэк яго завуць muxer ці router, але ўсё гэта адно і тое ж. Функцыя мультыплексара заключаецца ў аналізе шляху запыту і выбары адпаведнага апрацоўшчыка.

Калі ж патрэбна падтрымка складанай маршрутызацыі, тады лепш скарыстацца іншымі бібліятэкамі. Адны з найбольш прасунутых. gorilla/mux и go-chi/chi, гэтыя бібліятэкі даюць магчымасць рэалізаваць прамежкавую апрацоўку без асаблівых праблем. З іх дапамогай можна наладзіць wildcard-маршрутызацыю і выканаць шэраг іншых задач. Іх плюс - сумяшчальнасць са стандартнымі HTTP-апрацоўшчыкамі. У выніку вы можаце напісаць просты код з магчымасцю яго мадыфікацыі ў будучыні.

Праца са складанымі фрэймворкамі ў звычайнай сітуацыі запатрабуе не зусім стандартных рашэнняў, а гэта значна ўскладняе выкарыстанне дэфолтных апрацоўшчыкаў. Для стварэння пераважнай большасці дадаткаў хопіць камбінацыі бібліятэкі па змаўчанні і нескладанага маршрутызатара.

Апрацоўка запытаў

Акрамя таго, нам неабходны кампанент, які будзе "слухаць" уваходныя злучэнні і перанакіроўваць усе запыты правільнаму апрацоўшчыку. З гэтай задачай без працы зладзіцца http.Server.

Ніжэй паказана, што сервер адказвае за ўсе задачы, якія маюць дачыненне да апрацоўкі злучэнняў. Гэта, напрыклад, праца па пратаколе TLS. Для рэалізацыі выкліку http.ListenAndServer выкарыстоўваецца стандартны HTTP-сервер.

Цяпер давайце разгледзім больш складаныя прыклады.

Даданне Let's Encrypt

Па змаўчанні наша дадатак працуе па HTTP-пратаколу, аднак рэкамендуецца выкарыстоўваць пратакол HTTPS. У Go гэта можна зрабіць без праблем. Калі вы атрымалі сертыфікат і закрыты ключ, тады дастаткова прапісаць ListenAndServeTLS з указаннем правільных файлаў сертыфіката і ключа.

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

Заўсёды можна зрабіць лепш.

давайце Encrypt дае бясплатныя сертыфікаты з магчымасцю аўтаматычнага абнаўлення. Для таго, каб скарыстацца сэрвісам, патрэбен пакет. autocert.

Самы просты спосаб яго наладзіць - скарыстацца метадам autocert.NewListener у камбінацыі з http.Serve. Метад дазваляе атрымліваць і абнаўляць TLS-сертыфікаты, у той час як HTTP-сервер апрацоўвае запыты:

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

Калі мы адкрыем у браўзэры example.com, то атрымаем HTTPS-адказ "Hello, world!".

Калі патрэбна больш дбайная настройка, то варта скарыстацца мэнэджарам autocert.Manager. Затым ствараем уласны інстанс http.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("", "")

Гэта просты спосаб рэалізацыі поўнай падтрымкі HTTPS з аўтаматычным абнаўленнем сертыфіката.

Даданне нестандартных маршрутаў

Дэфолтны маршрутызатар, уключаны ў стандартную бібліятэку, добры, але ён вельмі просты. У большасці прыкладанняў патрэбна больш складаная маршрутызацыя, уключаючы ўкладзеныя і wildcard-маршруты ці працэдуру ўсталёўкі шаблонаў і параметраў шляхоў.

У гэтым выпадку варта выкарыстоўваць пакеты gorilla/mux и go-chi/chi. З апошнім мы і навучымся працаваць – ніжэй паказаны прыклад.

Дадзена - файл api/v1/api.go, які змяшчае маршруты для нашага 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
}

Усталеўваны для маршрутаў прэфікс api/vq у асноўным файле.

Мы можам зрабіць гэта для нашага галоўнага router пад api/v1/ prefix back in main 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())

Прастата працы са складанымі маршрутамі ў Go робіць магчымым спрасціць структурызацыю з абслугоўваннем вялікіх комплексных прыкладанняў.

Праца з middleware

У выпадку прамежкавай апрацоўкі выкарыстоўваецца абгортванне аднаго HTTP-апрацоўшчыка іншым, што робіць магчымым хутка праводзіць аўтэнтыфікацыю, сціск, часопісаванне і некаторыя іншыя функцыі.

У якасці прыкладу разгледзім інтэрфейс http.Handler, з яго дапамогай напішам апрацоўшчык з аўтэнтыфікацыяй карыстачоў сэрвісу.

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

Ёсць іншыя маршрутызатары, напрыклад, chi, якія дазваляюць пашырыць функцыянальнасць прамежкавай апрацоўкі.

Праца са статычнымі файламі

У стандартную бібліятэку Go уваходзяць магчымасці для працы са статычным кантэнтам, уключаючы выявы, а таксама файлы JavaScript і CSS. Доступ да іх можна атрымаць праз функцыю http.FileServer. Яна вяртае апрацоўшчык, які раздае файлы з вызначанай дырэкторыі.

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

Абавязкова варта памятаць, што http.Dir выводзіць змест каталога ў тым выпадку, калі ў ім няма асноўнага файла index.html. У гэтым выпадку, каб не дапусціць кампраметацыі каталога, варта выкарыстоўваць пакет unindexed.

Карэктнае завяршэнне працы

У Go ёсць і такая функцыя, як карэктнае завяршэнне працы HTTP-сервера. Гэта можна зрабіць з дапамогай метаду Shutdown(). Сервер запускаецца ў гаруціне, а далей канал праслухоўваецца для прыёму сігналу перапынення. Як толькі сігнал атрыманы, сервер адключаецца, але не адразу, а праз некалькі секунд.

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)

У якасці заключэння

Go - магутная мова з практычна ўніверсальнай стандартнай бібліятэкай. Яе дэфолтныя магчымасці вельмі шырокія, а ўзмацніць іх можна пры дапамозе інтэрфейсаў - гэта дазваляе распрацоўваць сапраўды надзейныя HTTP-серверы.

Skillbox рэкамендуе:

Крыніца: habr.com

Дадаць каментар