Как установить тайм-аут для запросов http.Get () в Golang?

107

Я делаю сборщик URL-адресов в Go и имею список URL-адресов для получения. Я отправляю http.Get()запросы на каждый URL и получаю от них ответ.

resp,fetch_err := http.Get(url)

Как я могу установить индивидуальный тайм-аут для каждого запроса Get? (Время по умолчанию очень велико, и это делает мой сборщик очень медленным.) Я хочу, чтобы мой сборщик имел тайм-аут около 40-45 секунд, после чего он должен вернуть «запрос истек» и перейти к следующему URL-адресу.

Как я могу этого добиться?

пимд
источник
1
Просто сообщаю вам, ребята, что я нашел этот способ более удобным (тайм-аут набора не работает, если есть проблемы с сетью, по крайней мере, для меня): blog.golang.org/context
Audrius
@Audrius Есть идеи, почему тайм-аут набора не работает, когда есть проблемы с сетью? Думаю, я наблюдаю то же самое. Я думал, что для этого нужен DialTimeout?!?!
Иордания
@Jordan Трудно сказать, так как я не углублялся в код библиотеки. Я разместил свое решение ниже. Я уже довольно давно использую его в продакшене, и пока он "просто работает" (тм).
Audrius

Ответы:

256

Видимо в Go 1.3 http.Client есть поле Timeout

client := http.Client{
    Timeout: 5 * time.Second,
}
client.Get(url)

Это помогло мне.

Sparrovv
источник
10
Что ж, для меня этого достаточно. Рад, что немного прокрутил вниз :)
Джеймс Адам
5
Есть ли способ установить другой тайм-аут для каждого запроса?
Арно Ринкин,
11
Что произойдет, когда истечет время ожидания? Есть ли Getвозвращать ошибку? Я немного сбит с толку, потому что Godoc for Clientговорит: таймер продолжает работать после возврата Get, Head, Post или Do и прерывает чтение Response.Body. Так что это означает , что либо Get или чтение Response.Bodyможет быть прерван из -за ошибки?
Avi Flax
1
Вопрос, в чем разница между http.Client.Timeoutvs. http.Transport.ResponseHeaderTimeout?
Рой Ли
2
@Roylee Одно из основных отличий согласно документации: http.Client.Timeoutвключает время на чтение тела ответа, http.Transport.ResponseHeaderTimeoutно не включает его.
imwill
53

Вам необходимо настроить собственный клиент с собственным транспортом, который использует настраиваемую функцию набора номера, которая оборачивается вокруг DialTimeout .

Что - то вроде (полностью непроверенным ) это :

var timeout = time.Duration(2 * time.Second)

func dialTimeout(network, addr string) (net.Conn, error) {
    return net.DialTimeout(network, addr, timeout)
}

func main() {
    transport := http.Transport{
        Dial: dialTimeout,
    }

    client := http.Client{
        Transport: &transport,
    }

    resp, err := client.Get("http://some.url")
}
Волкер
источник
Большое спасибо! Это именно то, что я искал.
pymd 03
В чем преимущество использования net.DialTimeout над Transport.ResponseHeaderTimeout, описанным в ответе zzzz?
Daniele B
4
@ Даниэль Б: Вы задаете неправильный вопрос. Дело не в преимуществах, а в том, что делает каждый код. DialTimeouts включается, если сервер не может быть отключен, в то время как другие тайм-ауты срабатывают, если определенные операции с установленным соединением занимают слишком много времени. Если ваши целевые серверы устанавливают соединение быстро, но затем начинают медленно блокировать вас, тайм-аут набора не поможет.
Volker
1
@Volker, спасибо за ответ. На самом деле я тоже это понял: похоже, что Transport.ResponseHeaderTimeout устанавливает тайм-аут чтения, то есть тайм-аут после того, как соединение было установлено, а у вас тайм-аут набора. Решение dmichael имеет дело как с тайм-аутом набора, так и с тайм-аутом чтения.
Daniele B
1
@Jonno: В Go нет бросков. Это преобразования типов.
Volker
31

Чтобы добавить к ответу Волкера, если вы также хотите установить тайм-аут чтения / записи в дополнение к таймауту подключения, вы можете сделать что-то вроде следующего

package httpclient

import (
    "net"
    "net/http"
    "time"
)

func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) {
    return func(netw, addr string) (net.Conn, error) {
        conn, err := net.DialTimeout(netw, addr, cTimeout)
        if err != nil {
            return nil, err
        }
        conn.SetDeadline(time.Now().Add(rwTimeout))
        return conn, nil
    }
}

func NewTimeoutClient(connectTimeout time.Duration, readWriteTimeout time.Duration) *http.Client {

    return &http.Client{
        Transport: &http.Transport{
            Dial: TimeoutDialer(connectTimeout, readWriteTimeout),
        },
    }
}

Этот код протестирован и работает в продакшене. Полный текст тестов доступен здесь https://gist.github.com/dmichael/5710968.

Имейте в виду, что вам нужно будет создавать нового клиента для каждого запроса, поскольку он conn.SetDeadlineссылается на точку в будущем изtime.Now()

дмайкл
источник
Разве вы не должны проверить возвращаемое значение conn.SetDeadline?
Эрик Урбан
3
Этот тайм-аут не работает с соединениями keepalive, которые используются по умолчанию и которые, я полагаю, должны использовать большинство людей. Вот что я придумал, чтобы разобраться с этим: gist.github.com/seantalts/11266762
xitrium
Спасибо @xitrium и Эрику за дополнительный вклад.
dmichael
Я чувствую, что это не так, как вы сказали, что нам нужно будет создавать нового клиента для каждого запроса. Поскольку Dial - это функция, которая, я думаю, получает вызов каждый раз, когда вы отправляете каждый запрос в одном и том же клиенте.
A-letubby 06
Вы уверены, что вам каждый раз нужен новый клиент? Каждый раз, когда он набирает номер, вместо использования net.Dial он будет использовать функцию, которую создает TimeoutDialer. Это новое соединение, крайний срок которого оценивается каждый раз из нового вызова time.Now ().
Блейк Колдуэлл
16

Если вы хотите делать это по запросу, обработка ошибок игнорируется для краткости:

ctx, cncl := context.WithTimeout(context.Background(), time.Second*3)
defer cncl()

req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "https://google.com", nil)

resp, _ := http.DefaultClient.Do(req)
Чад Грант
источник
1
Дополнительная информация: для каждого документа крайний срок, установленный Context, включает также чтение Body, аналогично http.Client.Timeout.
kubanczyk
1
Должен быть принятый ответ для Go 1.7+. Для Go 1.13+ можно немного сократить с помощью NewRequestWithContext
kubanczyk
9

Быстрый и грязный способ:

http.DefaultTransport.(*http.Transport).ResponseHeaderTimeout = time.Second * 45

Это мутирующее глобальное состояние без какой-либо координации. Тем не менее, это может быть нормально для вашего сборщика URL. В противном случае создайте частный экземпляр http.RoundTripper:

var myTransport http.RoundTripper = &http.Transport{
        Proxy:                 http.ProxyFromEnvironment,
        ResponseHeaderTimeout: time.Second * 45,
}

var myClient = &http.Client{Transport: myTransport}

resp, err := myClient.Get(url)
...

Ничего из вышеперечисленного не тестировалось.

zzzz
источник
Пожалуйста, исправьте меня, но похоже, что ResponseHeaderTimeout касается тайм-аута чтения, то есть тайм-аута после того, как соединение было установлено. Наиболее полным решением является решение @dmichael, так как оно позволяет установить как тайм-аут набора, так и таймаут чтения.
Daniele B
http.DefaultTransport.(*http.Transport).ResponseHeaderTimeout = time.Second * 45помогите мне много в тесте записи для тайм-аута запроса. Большое спасибо.
lee
0

Вы можете использовать https://github.com/franela/goreq, который обрабатывает тайм-ауты модным и простым способом.

Маркоснил
источник
Эта библиотека выглядит потрясающе!
ChrisR
-1
timeout := time.Duration(5 * time.Second)
transport := &http.Transport{Proxy: http.ProxyURL(proxyUrl), ResponseHeaderTimeout:timeout}

Это может помочь, но обратите внимание, что ResponseHeaderTimeoutзапускается только после того, как соединение установлено.

T.Max
источник