Что такое Middleware в веб-разработке на Go и как его реализовать?

Ответ

Middleware (или "промежуточное ПО") в контексте веб-серверов на Go — это функция или компонент, который встраивается в цепочку обработки HTTP-запроса до того, как он достигнет основного обработчика (handler). Middleware позволяет выполнять сквозную логику для множества эндпоинтов.

По сути, это реализация паттерна Декоратор или Цепочка обязанностей.

Основные задачи, решаемые с помощью Middleware:

  • Логирование (Logging): Запись информации о каждом входящем запросе.
  • Аутентификация и авторизация: Проверка токенов, сессий, прав доступа.
  • Восстановление после паник (Recovery): Перехват паник в обработчиках, чтобы сервер не падал.
  • Сжатие (Compression): Сжатие ответов (например, с помощью gzip).
  • CORS (Cross-Origin Resource Sharing): Добавление необходимых заголовков для кросс-доменных запросов.
  • Добавление контекста: Обогащение context.Context запроса, например, request_id.

Реализация с net/http:

Идиоматичный способ реализации middleware в Go — это функция, которая принимает http.Handler и возвращает новый http.Handler.

package main

import (
    "log"
    "net/http"
    "time"
)

// loggingMiddleware логирует время выполнения запроса.
func loggingMiddleware(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.RequestURI, time.Since(start))
    })
}

// mainHandler - наш основной обработчик.
func mainHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello, World!"))
}

func main() {
    // Создаём основной обработчик.
    finalHandler := http.HandlerFunc(mainHandler)

    // Оборачиваем его в наш middleware.
    http.Handle("/", loggingMiddleware(finalHandler))

    log.Println("Server is starting on :8080...")
    http.ListenAndServe(":8080", nil)
}

Ключевые моменты:

  • Компонуемость: Middleware легко объединяются в цепочки: middleware1(middleware2(myHandler)).
  • Стандартный интерфейс: Использование http.Handler делает middleware совместимым со стандартной библиотекой и большинством фреймворков (например, Chi, Gorilla Mux).
  • Популярные фреймворки (Gin, Echo) предоставляют свои, более удобные способы определения и подключения middleware, но базовый принцип остаётся тем же.