Уроки модуля (5/5)
Мини-проект: HTTP-сервер
Пакета net/http stdlib достаточно для создания готового к работе HTTP-сервера: маршрутизация, обработчики, промежуточное программное обеспечение, корректное завершение работы. Никаких рамок не требуется. Для типичных REST API это основа.
Привет, мир
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello %s", r.URL.Path[1:])
})
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}http.ListenAndServe блокируется и возвращается только в случае ошибки. nil в качестве второго аргумента = «используйте DefaultServeMux».
Подпись обработчика
Все обработчики имеют одну и ту же подпись:
func(w http.ResponseWriter, r *http.Request)w http.ResponseWriter— вывод: заголовки, код состояния, тело.r *http.Request— ввод: метод, URL, заголовки, тело, контекст.
Типичный образец:
func ping(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK) // optional, defaults to 200
fmt.Fprintln(w, `{"ok":true}`)
}Маршрутизация с помощью ServeMux
*http.ServeMux — базовый маршрутизатор. Начиная с Go 1.22, он поддерживает шаблоны с параметрами метода и пути:
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
fmt.Fprintln(w, "user", id)
})
mux.HandleFunc("POST /users", createUser)
http.ListenAndServe(":8080", mux)Для более сложных маршрутизаторов (группы, интегрированное промежуточное программное обеспечение, регулярное выражение) есть chi, gorilla/mux, gin, echo. Stdlib охватывает 90% случаев.
Промежуточное ПО в качестве декоратора
Промежуточное ПО — это функция func(http.Handler) http.Handler:
func logMW(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start))
})
}
http.ListenAndServe(":8080", logMW(mux))Чтобы связать их: logMW(authMW(rateLimitMW(mux))). Каждое промежуточное ПО украшает следующее.
http.Server для точного управления
http.ListenAndServe(addr, h) — это ярлык. Для тайм-аутов, TLS и корректного завершения работы вам необходимо создать экземпляр http.Server:
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 2 * time.Minute,
}
log.Fatal(srv.ListenAndServe())Грациозное завершение работы с контекстом
Когда процесс получает SIGINT/SIGTERM, вы хотите завершить текущие запросы перед закрытием:
ctx, stop := signal.NotifyContext(context.Background(),
os.Interrupt, syscall.SIGTERM)
defer stop()
srv := &http.Server{Addr: ":8080", Handler: mux}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}()
<-ctx.Done() // wait for the signal
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Println("shutdown:", err)
}srv.Shutdown прекращает принимать новые соединения и ждет завершения активных (до истечения крайнего срока).
Чтение тела JSON
type CreateUserReq struct {
Name string `json:"name"`
}
func createUser(w http.ResponseWriter, r *http.Request) {
var req CreateUserReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// ... use req.Name
w.WriteHeader(http.StatusCreated)
}r.Body — это io.ReadCloser: сервер закрывает его автоматически, но json.NewDecoder не читает дальше первого объекта JSON (полезно для проверки ввода).
Упражнения
Зарегистрируйте обработчик приветствия по маршруту '/' с помощью http.HandleFunc.
Решение доступно после 3 попыток
Перейдите на сервер su:8080 с http.ListenAndServe и получите ошибку, которая не равна нулю.
Решение доступно после 3 попыток
Какая идиоматическая фирма обработчика HTTP в Go?
func H(???) { ... }