Как ограничить тело HTTP-запроса через MaxBytesReader в Go

Как ограничить тело HTTP-запроса через MaxBytesReader в Go
Содержание статьи

http.MaxBytesReader ограничивает чтение входящего тела HTTP-запроса. При попытке прочитать больше заданного числа байт возвращается ошибка *http.MaxBytesError. На Mentoring.digital разберём небольшой обработчик, который различает превышение лимита и другую ошибку чтения.

Ставим ограничение перед чтением тела

package demo

import (
    "errors"
    "fmt"
    "io"
    "net/http"
)

func Ingest(w http.ResponseWriter, r *http.Request) {
    const maxBody = 64 << 10

    r.Body = http.MaxBytesReader(w, r.Body, maxBody)
    defer r.Body.Close()

    body, err := io.ReadAll(r.Body)
    if err != nil {
        var tooLarge *http.MaxBytesError
        if errors.As(err, &tooLarge) {
            http.Error(w, "body too large",
                http.StatusRequestEntityTooLarge)
        } else {
            http.Error(w, "cannot read body",
                http.StatusBadRequest)
        }
        return
    }

    fmt.Fprintf(w, "%d\n", len(body))
}

В учебном примере предел равен 64 КиБ, то есть 65 536 байтам. Это выбранное для примера значение, а не универсальная рекомендация для любого API. Успешный ответ содержит число прочитанных байт. Разбор JSON и сохранение данных в этом обработчике не выполняются.

Ограничивающий объект устанавливается до io.ReadAll. Если сначала полностью прочитать тело, последующая обёртка уже не сможет ограничить это чтение.

Отличаем лимит от обычного конца потока

MaxBytesReader предназначен для входящих HTTP-тел. В отличие от простого ограничения через io.LimitReader, превышение отмечается отдельной ошибкой. Проверка через errors.As позволяет распознать её и вернуть статус 413.

После записи ошибки обработчик завершает работу. Закрытие обёртки закрывает исходный reader. Здесь defer обеспечивает закрытие как при успешном чтении, так и при раннем выходе.

Почему одного Content-Length недостаточно

Значение r.ContentLength может быть неизвестным. Ограничение фактически прочитанных байт должно действовать независимо от наличия заявленного размера. Проверку заголовка можно использовать дополнительно, но решение о лимите в примере опирается на чтение потока.

ReadAll всё равно собирает разрешённое тело в памяти. Если API должен принимать большие данные потоком, отдельно проектируйте потоковую обработку и её ограничения.

Какие запросы проверить

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

Такие обработчики можно изучать на курсах Go с практикой HTTP. Лимиты конкретного API и обработку ошибок разберите с наставником по Go.

Похожие статьи по данной теме