GinMiddleCoding

Как создать и зарегистрировать собственный middleware в Gin?

Middleware в Gin — это func(*gin.Context) с вызовом c.Next() для передачи управления; регистрируется через r.Use() глобально, через Group().Use() для группы или прямо в маршруте.

Создание и регистрация middleware в Gin

Middleware в Gin — это функция типа gin.HandlerFunc, то есть func(*gin.Context). Ключевой момент: вызов c.Next() передаёт управление следующему handler-у в цепочке, а код после него выполняется на обратном ходу (как defer).

package middleware

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

	"github.com/gin-gonic/gin"
)

// RequestLogger логирует метод, путь и время выполнения.
func RequestLogger() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()

		// Код ДО handler-а
		c.Next() // вызов следующего handler-а

		// Код ПОСЛЕ handler-а (обратный ход)
		duration := time.Since(start)
		slog.Info("request",
			"method", c.Request.Method,
			"path", c.Request.URL.Path,
			"status", c.Writer.Status(),
			"duration", duration,
		)
	}
}

// RequireAuth проверяет Bearer-токен.
func RequireAuth(secret string) gin.HandlerFunc {
	return func(c *gin.Context) {
		token := c.GetHeader("Authorization")
		if token != "Bearer "+secret {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
				"error": "unauthorized",
			})
			return // Abort уже остановил цепочку, но return обязателен
		}
		c.Next()
	}
}

Регистрация middleware бывает трёх уровней:

package main

import (
	"github.com/gin-gonic/gin"
	"myapp/middleware"
)

func main() {
	r := gin.New() // без встроенного logger/recovery

	// 1. Глобальный уровень — применяется ко всем маршрутам
	r.Use(middleware.RequestLogger())
	r.Use(gin.Recovery()) // встроенный recovery от паник

	// 2. Уровень группы — только для маршрутов внутри /api
	api := r.Group("/api", middleware.RequireAuth("secret-token"))
	{
		api.GET("/profile", profileHandler)
		api.GET("/settings", settingsHandler)
	}

	// 3. Уровень маршрута — только для конкретного endpoint
	r.DELETE("/admin/users/:id", middleware.RequireAuth("admin-secret"), deleteUserHandler)

	r.Run(":8080")
}

func profileHandler(c *gin.Context)  { c.JSON(200, gin.H{"user": "alice"}) }
func settingsHandler(c *gin.Context) { c.JSON(200, gin.H{"theme": "dark"}) }
func deleteUserHandler(c *gin.Context) {
	c.JSON(200, gin.H{"deleted": c.Param("id")})
}

Middleware может хранить данные в контексте для передачи вниз по цепочке:

func InjectUserID() gin.HandlerFunc {
	return func(c *gin.Context) {
		userID := extractUserIDFromToken(c.GetHeader("Authorization"))
		c.Set("userID", userID) // записываем в контекст
		c.Next()
	}
}

// В handler-е:
func profileHandler(c *gin.Context) {
	userID, _ := c.Get("userID")
	c.JSON(200, gin.H{"user_id": userID})
}

func extractUserIDFromToken(auth string) string {
	// упрощённая заглушка
	if len(auth) > 7 {
		return auth[7:]
	}
	return ""
}

Подводные камни

  • Если не вызвать c.Next() и не вызвать c.Abort(), последующие handler-ы в цепочке просто не выполнятся — молчаливый баг.
  • c.Abort() выставляет флаг, но не останавливает текущую функцию middleware — нужен явный return после вызова.
  • Порядок r.Use() имеет значение: middleware, зарегистрированный после r.Group(), не применяется к маршрутам этой группы, если группа создана до вызова Use.
  • c.Set/c.Get не потокобезопасны вне goroutine текущего запроса — не передавайте *gin.Context в фоновые горутины; копируйте нужные данные до запуска горутины.
  • Паника внутри middleware без gin.Recovery() уронит весь сервер; не используйте gin.New() без явного добавления Recovery.
  • Большое количество middleware, измеряющих время через time.Now(), складывается в заметный overhead на hot-path — профилируйте перед добавлением каждого нового слоя.
  • Middleware, написанный как замыкание с состоянием (например, счётчик), должен защищать shared state мьютексом или использовать sync/atomic.

Common mistakes

  • Давать ответ про собственный middleware в Gin только на уровне определения, не показывая поведение в реальном приложении.
  • Игнорировать границы ответственности вокруг темы «собственный middleware в Gin»: кто отменяет работу, кто владеет ресурсом и где формируется ответ клиенту.
  • Не связывать собственный middleware в Gin с observability, тестированием или безопасностью, когда это влияет на продакшен-поведение.

What the interviewer is testing

  • Точно объясняет, что именно делает собственный middleware в Gin и где это используется в Go-коде.
  • Связывает собственный middleware в Gin с корректным lifecycle запроса, отменой, конкурентностью или конфигурацией сервера там, где это уместно.
  • Не изобретает API и опирается на реальные контракты официальной документации.

Sources

Related topics