Как валидировать и ограничивать размер 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.
- Называет реальные риски, диагностику и критерий корректности.
- Связывает ответ с текущей документацией и миграционными ограничениями.