Разработване на уеб сървъри в Golang - от прости до сложни

Разработване на уеб сървъри в Golang - от прости до сложни

Преди пет години започнах разработете Gophish, направи възможно изучаването на Golang. Разбрах, че Go е мощен език, който се допълва от много библиотеки. Go е многофункционален: по-специално можете лесно да разработвате приложения от страната на сървъра с него.

Тази статия е за писане на сървър в Go. Нека започнем с прости неща като „Здравей свят!“ и завършим с приложение със следните функции:

- Използване на Let's Encrypt за HTTPS.
- Работете като API рутер.
- Работа с мидълуер.
- Работа със статични файлове.
- Правилно изключване.

Skillbox препоръчва: Практически курс „Разработчик на Python от нулата“.

Напомняме ви: за всички читатели на "Habr" - отстъпка от 10 000 рубли при записване във всеки курс Skillbox, използвайки промоционалния код на "Habr".

Здравей, свят!

Създаването на уеб сървър в 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)
}

След това, ако стартирате приложението и отворите страницата Localhost, тогава веднага ще видите текста "Здравей, свят!" (разбира се, ако всичко работи правилно).

Ще използваме манипулатора многократно по-долу, но първо нека разберем как работи всичко.

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 са представени от един интерфейс, който дава много възможности на програмиста. Така например междинният софтуер се внедрява с помощта на манипулатор, където ServeHTTP първо прави нещо и след това извиква метода ServeHTTP на друг манипулатор.

Както бе споменато по-горе, манипулаторите просто формират отговори на заявки. Но кой манипулатор трябва да се използва в определен момент?

Заявка за маршрутизиране

За да направите правилния избор, използвайте HTTP мултиплексора. В редица библиотеки се нарича muxer или router, но всички те са еднакви. Функцията на мултиплексора е да анализира пътя на заявката и да избере подходящия манипулатор.

Ако имате нужда от поддръжка за сложно маршрутизиране, тогава е по-добре да използвате библиотеки на трети страни. Един от най-напредналите горила/мукс и го-чи/чи, тези библиотеки позволяват внедряването на междинна обработка без никакви проблеми. С тяхна помощ можете да настроите маршрутизиране с заместващи знаци и да изпълнявате редица други задачи. Предимството им е съвместимостта със стандартните HTTP манипулатори. В резултат на това можете да напишете прост код, който може да бъде модифициран в бъдеще.

Работата със сложни рамки в нормална ситуация ще изисква не съвсем стандартни решения и това значително усложнява използването на манипулатори по подразбиране. Комбинацията от библиотека по подразбиране и обикновен рутер ще бъде достатъчна за създаване на по-голямата част от приложенията.

Обработка на заявки

Освен това се нуждаем от компонент, който ще "слуша" за входящи връзки и ще пренасочва всички заявки към правилния манипулатор. http.Server може лесно да се справи с тази задача.

Следното показва, че сървърът е отговорен за всички задачи, свързани с обработката на връзки. Това е например работа по протокола TLS. Използва се стандартен HTTP сървър за изпълнение на повикването http.ListenAndServer.

Сега нека разгледаме по-сложни примери.

Добавяне на Let's Encrypt

По подразбиране нашето приложение работи по HTTP протокола, но се препоръчва използването на HTTPS протокола. В Go това може да стане без проблеми. Ако сте получили сертификат и частен ключ, тогава е достатъчно да напишете ListenAndServeTLS с правилния сертификат и ключови файлове.

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

Винаги може по-добре.

Нека да шифроваме дава безплатни сертификати с възможност за автоматично подновяване. За да използвате услугата, ви е необходим пакет autocert.

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

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

Ако отворим в браузъра example.com, получаваме HTTPS отговор „Здравей, свят!“.

Ако имате нужда от по-подробна конфигурация, тогава трябва да използвате 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 поддръжка с автоматично подновяване на сертификата.

Добавяне на персонализирани маршрути

Рутерът по подразбиране, включен в стандартната библиотека, е хубав, но много основен. Повечето приложения се нуждаят от по-сложно маршрутизиране, включително вложени маршрути и маршрути със заместващи знаци, или задаване на модели и параметри на пътя.

В този случай трябва да използвате пакети горила/мукс и го-чи/чи. Ще научим как да работим с последния - пример е показан по-долу.

Даден е файлът 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 за маршрутите в основния файл.

След това можем да монтираме това към основния ни рутер под префикса api/v1/ обратно в основното ни приложение:

// 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 прави възможно опростяването на структурирането и поддръжката на големи сложни приложения.

Работа с мидълуер

В случай на междинна обработка се използва обвиване на един 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(). Сървърът се стартира в goroutine и след това каналът се прослушва за получаване на сигнал за прекъсване. Веднага след получаване на сигнала сървърът се изключва, но не веднага, а след няколко секунди.

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 препоръчва:

Източник: www.habr.com

Добавяне на нов коментар