http.Client.Timeout может ломать скачивание больших файлов
Представим, что мы скачиваем большой файл из внешнего API.
Код выглядит вполне нормально:
client := &http.Client{ Timeout: 5 * time.Second, } resp, err := client.Get(fileURL) if err != nil { return err } defer resp.Body.Close() _, err = io.Copy(dst, resp.Body) return err
Сервер быстро прислал заголовки, запрос успешно начался.
Но файл большой, соединение медленное или сервер на какое-то время перестал передавать данные. Через пять секунд клиент может прервать операцию, даже если ответ уже получен.
Почему? Потому что http.Client.Timeout ограничивает всё время выполнения http-ручки, включая чтение тела ответа. Это не просто таймаут на подключение или ожидание первых байтов.
Что с этим делать?
Всё зависит от задачи.
Для обычного API-запроса общий таймаут часто вполне подходит. Для скачивания больших файлов может потребоваться отдельная стратегия: собственный HTTP-клиент, отмена через context.Context и ограничения, соответствующие требованиям приложения.
Например, можно создать клиент без общего таймаута: client := &http.Client{} А время выполнения контролировать через контекст: ctx, cancel := context.WithTimeout( context.Background(), 2*time.Minute, ) defer cancel() req, err := http.NewRequestWithContext( ctx, http.MethodGet, fileURL, nil, ) if err != nil { return err } resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() _, err = io.Copy(dst, resp.Body) return err
Теперь на скачивание отводится две минуты.
Но есть важный нюанс: это всё ещё общий дедлайн, а не таймаут бездействия, который сбрасывается при поступлении очередной порции данных. Если нужно прерывать загрузку только тогда, когда данные долго не поступают, потребуется другая логика.
Вывод
Таймауты решают разные задачи.
Перед настройкой стоит понять, что именно мы ограничиваем: установление соединения, ожидание заголовков, простой во время передачи данных или всю операцию целиком.
В Go неправильно выбранный таймаут может превратить вполне рабочую загрузку файла в баг на продакшене.
Документация: https://pkg.go.dev/net/http