Go context.WithTimeout: ограничить HTTP-запрос
context.WithTimeout создаёт контекст, который отменяется через заданный интервал. Его передают в HTTP-запрос, запрос к базе или горутину, чтобы операция не висела бесконечно. Функция возвращает контекст и cancel, который нужно вызывать всегда — иначе таймер и связанные ресурсы живут до истечения срока.
Проверить срабатывание таймаута
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
select {
case <-time.After(200 * time.Millisecond):
fmt.Println("слишком долго")
case <-ctx.Done():
fmt.Println("таймаут:", ctx.Err())
}
}Через 50 мс контекст закрывает канал Done(), а ctx.Err() возвращает context deadline exceeded. Если операция завершилась раньше, cancel() освобождает таймер, не дожидаясь дедлайна. Поэтому defer cancel() ставят сразу после создания контекста, даже если ошибка кажется невозможной.
Отправить HTTP-запрос с дедлайном
package main
import (
"context"
"fmt"
"io"
"net/http"
"time"
)
func fetch(ctx context.Context, url string) (string, error) {
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return "", err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
return string(body), err
}
func main() {
body, err := fetch(context.Background(), "https://example.com")
if err != nil {
fmt.Println("ошибка:", err)
return
}
fmt.Println(len(body))
}http.NewRequestWithContext связывает запрос с контекстом: при отмене транспорт прерывает соединение, а Do возвращает ошибку, которую можно проверить через errors.Is(err, context.DeadlineExceeded). В обработчике HTTP используйте r.Context() как родителя, чтобы отмена клиентом тоже останавливала работу.
http.Client.Timeout ограничивает весь запрос, включая чтение тела, но не помогает передать дедлайн в базу. Контекст удобнее для цепочки вызовов: передавайте его первым аргументом и не сохраняйте в структуре надолго. Контекст не прерывает код, который не проверяет Done(); циклы и тяжёлые вычисления должны сами реагировать на отмену.
Не создавайте контекст с таймаутом на весь сервис и не используйте context.Background() внутри обработчика. Для фоновых задач с собственным сроком жизни Background() уместен, но отмену всё равно нужно связать с остановкой приложения.
Документация: context, net/http Request.WithContext.