Gzip-ответ занял слишком много памяти в Go: где ставить лимит чтения
Небольшой сжатый ответ после распаковки оказался огромным. Ограничение только размера входного архива не гарантирует ограничение результата. При работе с недоверенным gzip нужно отдельно контролировать объём распакованных данных и время обработки.
Как разобрать ситуацию
В примере gzip.Reader обёрнут io.LimitReader с пределом limit+1. Дополнительный байт позволяет отличить допустимое содержимое ровно на границе от превышения. Если прочитано больше limit, результат отклоняется, а не передаётся дальше как незаметно обрезанный документ. Закрытие reader и исходного тела остаётся обязанностью вызывающего кода.
Пример и ожидаемый результат
Учебная программа сжимает 100 одинаковых символов и пробует получить результат при допустимом размере 10. Она читает максимум 11 распакованных байт и обнаруживает превышение. Внешний URL не вызывается: источник данных создаётся прямо в памяти.
package main
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"strings"
)
func main() {
var b bytes.Buffer
w := gzip.NewWriter(&b)
if _, e := w.Write([]byte(strings.Repeat("x", 100))); e != nil {
panic(e)
}
if e := w.Close(); e != nil {
panic(e)
}
r, e := gzip.NewReader(&b)
if e != nil {
panic(e)
}
defer r.Close()
out, e := io.ReadAll(io.LimitReader(r, 11))
if e != nil {
panic(e)
}
fmt.Println(len(out), len(out) > 10)
}
Программа выводит 11 true. Это означает, что превышение найдено ограниченным чтением; весь распакованный документ не был принят приложением.
Проверка перед применением
- Проверьте размеры limit−1, limit и limit+1 на распакованном содержимом.
- Ограничьте также время и размер сжатого входа: один порог не покрывает все виды нагрузки.
- Не возвращайте усечённый JSON или документ как успешный полный результат.
Если содержимое принято в пределах лимита, всё равно проверяйте его тип и полноту по контракту следующего шага. Лимит отвечает на вопрос ресурса, а не подлинности или корректности данных. Такой тест полезно держать рядом с обычным небольшим корректным ответом, чтобы защита не ломала нормальный рабочий сценарий.
Границы решения
Пример не является полноценной защитой любого архивного формата и не проверяет HTTP-клиент. При автоматической распаковке Transport уточните, на каком уровне вы фактически читаете байты. Память и CPU нужно контролировать вместе.
Если базовый Go уже знаком и хочется связать HTTP, хранение данных и обработку отказов в одну систему, посмотрите наш платный курс
Микросервисы на Go: HTTP, PostgreSQL, gRPC, Kafka и Docker. Перед записью сравните программу со своей задачей и подготовкой.
Техническая справка: pkg.go.dev: документация, pkg.go.dev: документация.
Следующий шаг: Go backend: HTTP, конкурентность и устойчивость сервиса. По смежным вопросам: Отмена HTTP-запроса в Go: как убедиться, что context дошёл до клиента, HTTP retry в Go: почему один список кодов не делает повтор безопасным.
