GinMiddleTechnical

Как валидировать и ограничивать размер request body, чтобы не получить DoS?

Оберните Request.Body в http.MaxBytesReader до вызова ShouldBind, установите ReadTimeout на http.Server и проверяйте ошибку &http.MaxBytesError при парсинге тела.

Ограничение размера request body в Gin

Без ограничений клиент может отправить тело произвольного размера и заставить сервер выделить столько же памяти. Защита строится на трёх уровнях: таймаут TCP-чтения, ограничение размера тела и явная проверка ошибки превышения.

Уровень 1: ReadTimeout на http.Server

Ограничивает общее время чтения запроса (заголовки + тело). Защищает от slow-body атак.

srv := &http.Server{
	Addr:        ":8080",
	Handler:     router,
	ReadTimeout: 10 * time.Second, // полное время чтения запроса
}

Уровень 2: http.MaxBytesReader в middleware

http.MaxBytesReader оборачивает io.Reader и возвращает ошибку, когда прочитано больше лимита. Устанавливайте его глобально через middleware:

func MaxBodySize(limit int64) gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, limit)
		c.Next()
	}
}

func main() {
	router := gin.New()
	router.Use(gin.Recovery())
	router.Use(MaxBodySize(4 << 20)) // 4 MB для всех маршрутов

	// Для отдельных маршрутов можно задать другой лимит:
	upload := router.Group("/upload")
	upload.Use(MaxBodySize(50 << 20)) // 50 MB для загрузки файлов
	upload.POST("/avatar", uploadHandler)
}

Уровень 3: Обработка ошибки превышения лимита

При парсинге тела через c.ShouldBindJSON или ручное чтение нужно проверять тип ошибки:

func createItemHandler(c *gin.Context) {
	var req CreateItemRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		var maxBytesErr *http.MaxBytesError
		if errors.As(err, &maxBytesErr) {
			c.JSON(http.StatusRequestEntityTooLarge, gin.H{
				"error": "request body too large",
				"limit": maxBytesErr.Limit,
			})
			return
		}
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusCreated, gin.H{"id": "123"})
}

Валидация структуры запроса

После парсинга дополнительно валидируйте поля через теги binding (используется github.com/go-playground/validator/v10):

type CreateItemRequest struct {
	Name    string `json:"name" binding:"required,max=255"`
	Content string `json:"content" binding:"required,max=65536"`
	Tags    []string `json:"tags" binding:"max=10,dive,max=50"`
}

Content-Length заголовок

Проверка Content-Length быстрее, но ненадёжна — клиент может прислать неверный заголовок или chunked encoding без него. Используйте как дополнительный быстрый отказ:

func RejectLargeContentLength(limit int64) gin.HandlerFunc {
	return func(c *gin.Context) {
		if c.Request.ContentLength > limit {
			c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, gin.H{
				"error": "content-length exceeds limit",
			})
			return
		}
		c.Next()
	}
}

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

  • MaxBytesReader не вызван до чтения тела — если тело уже начало читаться (например, другим middleware), ограничение не сработает.
  • Порядок middleware важенMaxBodySize должен быть зарегистрирован до любого middleware, который читает тело (например, логирования с body capture).
  • Multipart/form-data требует отдельной настройки — для форм используйте c.Request.ParseMultipartForm(maxMemory) с явным лимитом; MaxBytesReader ограничивает только поток чтения, но не предотвращает запись во временные файлы.
  • gzip тела обходит лимит — если принимаете сжатые тела, ограничивайте размер после распаковки, иначе небольшой сжатый запрос может распаковаться в гигабайты (zip bomb).
  • Разные лимиты для разных эндпоинтов — повторный вызов MaxBytesReader в хендлере перезапишет middleware-уровень; убедитесь, что логика согласована.
  • 413 vs 400 — превышение размера это HTTP 413 (Request Entity Too Large), не 400; клиенты могут обрабатывать их по-разному.

Common mistakes

  • Отвечать определением без production-сценария.
  • Не называть runtime boundary, security boundary или failure mode.
  • Игнорировать версию API, observability и тестовую проверку.

What the interviewer is testing

  • Объясняет механизм своими словами и без выдуманных API.
  • Называет реальные риски, диагностику и критерий корректности.
  • Связывает ответ с текущей документацией и миграционными ограничениями.

Sources

Related topics