Menu
Coddy logo textTech

Middleware как Декоратор

Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 97 из 107.

Middleware — это одно из наиболее практичных применений паттерна Декоратор в Go. В веб-разработке функции middleware оборачивают HTTP-обработчики для добавления сквозной функциональности, такой как логирование, аутентификация или замер времени выполнения, без изменения исходного обработчика.

Ключевая идея заключается в том, что middleware принимает обработчик и возвращает новый обработчик с добавленным поведением. Это в точности соответствует паттерну Декоратор — тот же интерфейс на входе и выходе, но с расширенной функциональностью:

type Handler func(request string) string

func LoggingMiddleware(next Handler) Handler {
    return func(request string) string {
        result := next(request)
        return "[LOG] " + result
    }
}

func AuthMiddleware(next Handler) Handler {
    return func(request string) string {
        return "[AUTH] " + next(request)
    }
}

Каждый промежуточный слой (middleware) оборачивает следующий обработчик в цепочке, добавляя свое поведение до или после вызова обернутого обработчика. Вы можете объединять несколько слоев middleware, компонуя их:

func MainHandler(request string) string {
    return "Response to: " + request
}

handler := LoggingMiddleware(AuthMiddleware(MainHandler))
fmt.Println(handler("GET /users"))
// [LOG] [AUTH] Ответ на: GET /users

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

challenge icon

Задание

Легко

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

Вы организуете свой код в трех файлах:

  • handler.go: Определите тип обработчика и базовый обработчик, который обрабатывает запросы.

    Создайте тип Handler как функцию, которая принимает строку запроса и возвращает строку ответа:

    type Handler func(request string) string

    Реализуйте функцию BaseHandler, которая возвращает Processed: [request] для любого полученного запроса.

  • middleware.go: Создайте функции промежуточного ПО, которые оборачивают обработчики дополнительным поведением.

    Создайте три функции middleware, каждая из которых принимает Handler и возвращает новый Handler:

    • TimingMiddleware — оборачивает ответ информацией о времени, возвращая [TIMING] [response]
    • ValidationMiddleware — добавляет префикс валидации, возвращая [VALID] [response]
    • UppercaseMiddleware — преобразует весь ответ в верхний регистр

    Каждое промежуточное ПО должно вызывать обернутый обработчик и дополнять его результат.

  • main.go: Постройте конвейер обработки путем композиции middleware.

    Прочитайте количество слоев middleware, которые нужно применить. Затем для каждого слоя прочитайте тип middleware (timing, validation или uppercase) и оберните текущий обработчик этим middleware. Применяйте их в том порядке, в котором они считываются — первое считанное middleware становится самой внешней оболочкой.

    После создания конвейера прочитайте строку запроса, обработайте ее через созданный обработчик и выведите результат.

Будут предоставлены следующие входные данные:

  • Строка 1: Количество слоев middleware
  • Следующие строки: По одному типу middleware на строку (timing, validation или uppercase)
  • Последняя строка: Запрос для обработки

Например, при вводе:

2
timing
validation
GET /users

Ваш вывод должен быть:

[TIMING] [VALID] Processed: GET /users

И при вводе:

3
uppercase
timing
validation
POST /data

Ваш вывод должен быть:

[TIMING] [VALID] PROCESSED: POST /DATA

И при вводе:

1
uppercase
hello world

Ваш вывод должен быть:

PROCESSED: HELLO WORLD

И при вводе:

0
simple request

Ваш вывод должен быть:

Processed: simple request

Обратите внимание, как порядок оборачивания middleware влияет на результат — первое примененное middleware (самое внешнее) запускается первым и видит окончательный результат от всех внутренних middleware. Второе middleware в примере с uppercase запускается последним (самым внутренним), поэтому оно переводит базовый ответ в верхний регистр до того, как timing и validation добавят свои префиксы!

Шпаргалка

Промежуточное ПО (Middleware) в Go следует паттерну «Декоратор», оборачивая обработчики для добавления функциональности без изменения исходного обработчика.

Определите тип обработчика как функцию:

type Handler func(request string) string

Функции Middleware принимают обработчик и возвращают новый обработчик с расширенным поведением:

func LoggingMiddleware(next Handler) Handler {
    return func(request string) string {
        result := next(request)
        return "[LOG] " + result
    }
}

func AuthMiddleware(next Handler) Handler {
    return func(request string) string {
        return "[AUTH] " + next(request)
    }
}

Объединяйте несколько middleware в стек путем их композиции:

func MainHandler(request string) string {
    return "Response to: " + request
}

handler := LoggingMiddleware(AuthMiddleware(MainHandler))
fmt.Println(handler("GET /users"))
// Output: [LOG] [AUTH] Response to: GET /users

Самый внешний middleware запускается первым, определяя порядок выполнения. Каждый middleware может добавлять поведение до или после вызова обернутого обработчика.

Попробуйте сами

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Прочитать количество слоев middleware
	nLine, _ := reader.ReadString('\n')
	n, _ := strconv.Atoi(strings.TrimSpace(nLine))

	// Прочитать типы middleware
	middlewareTypes := make([]string, n)
	for i := 0; i < n; i++ {
		line, _ := reader.ReadString('\n')
		middlewareTypes[i] = strings.TrimSpace(line)
	}

	// Прочитать запрос
	request, _ := reader.ReadString('\n')
	request = strings.TrimSpace(request)

	// TODO: Начните с BaseHandler

	// TODO: Постройте конвейер (pipeline), обернув обработчик в middleware
	// Примените middleware по порядку — первое прочитанное middleware становится самой внешней оберткой
	// Hint: для этого переберите middlewareTypes в ОБРАТНОМ порядке
	// Последнее примененное middleware будет самым внешним (выполняется первым)

	// TODO: Обработайте запрос через составной обработчик и выведите результат
	fmt.Println("TODO: implement pipeline")
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование