
Пет години назад започнах , което ми позволи да се запозная с Golang. Осъзнах, че Go е мощен език, чиито възможности се допълват от много библиотеки. Go е универсален: по-специално, с него можете без проблем да разработвате сървърни приложения.
Тази статия е посветена на написването на сървър на Go. Нека да започнем с прости неща, като „Hello world!“, и да завършим с приложение, което има следните възможности:
— Използване на Let’s Encrypt за HTTPS.
— Работа в ролята на API маршрутизатор.
— Работа с middleware.
— Обработка на статични файлове.
— Коректно завършване на работата.
Skillbox препоръчва: Практически курс .
Напомняме: за всички читатели на "Хабра" — отстъпка от 10 000 рубли при записване на всякакъв курс на Skillbox с промокод "Хабр".
Hello, world!
Можете много бързо да създадете уеб-сървър на 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
В примера беше използван пакетът , който е основното средство в 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, но всичко е едно и също. Функцията на мултиплексора е да анализира пътя на заявката и да избере съответния обработчик.
Ако обаче е нужна поддръжка на сложна маршрутизация, по-добре е да се използват външни библиотеки. Някои от най-развитите са и , тези библиотеки дават възможност за реализиране на междинна обработка без особени проблеми. С тяхна помощ можете да настроите wildcard маршрутизация и да изпълните редица други задачи. Плюсът им е съвместимостта със стандартните HTTP обработчици. В резултат можете да напишете прост код с възможност за модификация в бъдеще.
Работата със сложни фреймворкове в обичайна ситуация изисква нестандартни решения, което значително усложнява използването на дефолтни обработчици. За създаването на преобладаващото мнозинство приложения е достатъчна комбинация от библиотека по подразбиране и несложен маршрутизатор.
Обработка на заявки
Също така, ни е необходим компонент, който да „слуша“ входящите връзки и да пренасочва всички заявки към правилния обработчик. С тази задача лесно се справя http.Server.
По-долу е показано, че сървърът отговаря за всички задачи, свързани с обработката на връзките. Това включва работа по протокола TLS. За реализиране на извикването http.ListenAndServe се използва стандартният HTTP сървър.
Сега нека разгледаме по-сложни примери.
Добавяне на 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)Ако отворим в браузъра , ще получим HTTPS отговор «Hello, world!».
Ако е необходима по-подробна конфигурация, би било добре да се използва мениджърът autocert.Manager. След това създаваме собствен екземпляр на http.Server (досега използвахме стандартния) и добавяме мениджъра в конфигурацията на TLS на сървъра:
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 маршрути или процедура за определяне на шаблони и параметри на пътищата.
В този случай е добре да се използват пакети и . С последния ще се научим да работим — по-долу е показан пример.
Даден е файл 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 позволява опростяване на структурированието за поддръжка на големи комплексни приложения.
Работа с 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
}
\/\/ Предполагайки, че аутентикацията е преминала, стартираме оригиналния обработчик
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. В такъв случай, за да се предотврати компрометирането на директорията, е необходимо да се използва пакет .
Коректно приключване на работата
В Go съществува и функция за коректно приключване на работа на HTTP сървъра. Това може да стане с метода Shutdown(). Сървърът стартира в горутина, а след това каналът се прослушва за получаване на сигнал за прекъсване. След като сигналът бъде получен, сървърът се изключва, но не веднага, а след няколко секунди.
handler := server.NewRouter()
srv := &http.Server{
Handler: handler,
}
go func() {
srv.Serve(autocert.NewListener(domains...))
}()
// Изчакайте прекъсване
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
// Опитайте коректно приключване
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(ctx)В заключение
Go е мощен език с практически универсална стандартна библиотека. Неговите основни възможности са много широки и могат да бъдат разширени чрез интерфейси — това позволява разработването на наистина надеждни HTTP сървъри.
Skillbox препоръчва:
- Двугодишен практически курс .
- Образователен онлайн курс .
- Практически годишен курс .
Източник: habr.com
