
Пет години назад започнах , което ми даде възможност да се запозная с Golang. Осъзнах, че Go е мощен език, чийто възможности се допълват от множество библиотеки. Go е универсален: по-специално, с него безпроблемно могат да се разработват сървърни приложения.
Тази статия е посветена на написването на сървър на Go. Да започнем с простите неща, като „Hello world!“, а да завършим с приложение с следните възможности:
— Използване на Let’s Encrypt за HTTPS.
— Работa като 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.ListenAndServer се използва стандартният 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 отговор «Здравей, свят!».
Ако е необходима по-подробна настройка, трябва да се използва мениджърът 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 маршрути или процедура за задаване на шаблони и параметри на пътищата.
В този случай е добре да се използват пакети и . С последния ще научим как да работим — по-долу е показан пример.
Даден — файл 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
