Как установить таймаут для http.Get() в Golang?
Правильный способ: используйте http.Client с Timeout
В Go стандартная функция http.Get() использует глобальный клиент http.DefaultClient, у которого таймаут по умолчанию не установлен (равен 0, что означает "без таймаута"). Чтобы добавить таймаут, нужно создать собственный экземпляр http.Client и задать поле Timeout.
Вот минимальный рабочий пример:
package main
import (
"fmt"
"io"
"net/http"
"time"
)
func main() {
// Создаём клиент с таймаутом 5 секунд
client := &http.Client{
Timeout: 5 * time.Second,
}
// Выполняем запрос через наш клиент
resp, err := client.Get("https://api.example.com/data")
if err != nil {
// Проверяем, является ли ошибка таймаутом
if isTimeoutError(err) {
fmt.Println("Ошибка: превышен таймаут запроса")
} else {
fmt.Printf("Ошибка при выполнении запроса: %v\n", err)
}
return
}
defer resp.Body.Close()
// Читаем тело ответа
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Ошибка чтения ответа: %v\n", err)
return
}
fmt.Printf("Статус: %s\n", resp.Status)
fmt.Printf("Тело ответа: %s\n", string(body))
}
// Вспомогательная функция для проверки ошибок таймаута
func isTimeoutError(err error) bool {
type timeout interface {
Timeout() bool
}
t, ok := err.(timeout)
return ok && t.Timeout()
}
Почему это работает?
Поле Timeout в http.Client включает в себя время на:
- Установку соединения (TCP handshake)
- TLS handshake (если используется HTTPS)
- Отправку запроса
- Чтение заголовков ответа
- Чтение тела ответа (включая все паузы между пакетами)
То есть это полный таймаут на весь HTTP-цикл. Если сервер не отвечает в течение указанного времени, клиент возвращает ошибку.
Более тонкая настройка таймаутов
Если вам нужно разделить таймауты на разные фазы (например, соединение должно установиться за 2 секунды, а чтение данных — за 3 секунды), используйте http.Transport:
client := &http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 2 * time.Second, // таймаут на установку соединения
KeepAlive: 30 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 2 * time.Second, // таймаут на TLS
ResponseHeaderTimeout: 3 * time.Second, // таймаут на получение заголовков
ExpectContinueTimeout: 1 * time.Second,
},
Timeout: 10 * time.Second, // общий таймаут (перекрывает частные)
}
Обработка ошибок таймаута
Ошибки таймаута в Go реализуют интерфейс net.Error с методом Timeout() bool. Вы можете проверять это разными способами:
// Способ 1: приведение типа
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
fmt.Println("Таймаут!")
}
// Способ 2: использование errors.As (Go 1.13+)
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
fmt.Println("Таймаут!")
}
// Способ 3: проверка на конкретные ошибки
if errors.Is(err, context.DeadlineExceeded) {
fmt.Println("Таймаут через контекст!")
}
Важное замечание
Не используйте http.Get() в production-коде без явного указания таймаута. Всегда создавайте собственный клиент с осмысленными значениями таймаутов. Это предотвратит зависание программы и утечку горутин.
Альтернативный подход: использование контекста (context.Context)
Другой способ установить таймаут — использовать context.WithTimeout вместе с http.NewRequestWithContext(). Этот метод даёт больше гибкости, например, возможность отменять запрос из нескольких мест одновременно.
package main
import (
"context"
"fmt"
"io"
"net/http"
"time"
)
func main() {
// Создаём контекст с таймаутом 5 секунд
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() // обязательно вызываем cancel для освобождения ресурсов
// Создаём запрос с контекстом
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.example.com/data", nil)
if err != nil {
fmt.Printf("Ошибка создания запроса: %v\n", err)
return
}
// Выполняем запрос через стандартный клиент (или свой)
resp, err := http.DefaultClient.Do(req)
if err != nil {
// Проверяем, не из-за таймаута ли ошибка
if ctx.Err() == context.DeadlineExceeded {
fmt.Println("Запрос превысил таймаут")
} else {
fmt.Printf("Ошибка выполнения запроса: %v\n", err)
}
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
Плюсы этого подхода:
- Контекст можно передавать вниз по цепочке вызовов (например, в базу данных или другие HTTP-запросы)
- Легко комбинировать таймаут с отменой по сигналу (Ctrl+C) через
signal.NotifyContext - Более идиоматичный Go-стиль для сложных приложений
Минусы: требует больше кода, чем просто client.Timeout. Для простых скриптов или однократных запросов лучше использовать первый подход с http.Client.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги
Частые ошибки в Python и способы их исправления: От IndentationError до RecursionError
Kotlin для машинного обучения: возможности и ограничения