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


106

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

resp,fetch_err := http.Get(url)

Як я можу встановити нестандартний час очікування для кожного запиту Get? (Час за замовчуванням дуже довгий, і це робить мого вихователя дуже повільним.) Я хочу, щоб у мого виборця був час очікування приблизно 40-45 секунд, після якого він повинен повернути "час вимкнення запиту" і перейти до наступної URL-адреси.

Як я можу цього досягти?


1
Просто даючи вам , хлопці , знаєте , що я знайшов цей спосіб більш зручний (тайм - аут набору не працює добре , якщо є проблеми мережі, по крайней мере , для мене): blog.golang.org/context
Аудрюс

@Audrius Будь-яка ідея, чому тайм-аут набору номера не працює, коли виникають проблеми з мережею? Я думаю, що бачу те саме. Я думав, що це DialTimeout?!?!
Йорданія

@ Джордан Важко сказати, як я не занурився так глибоко в бібліотечний код. Я розмістив своє рішення нижче. Я використовую його у виробництві вже досить давно, і поки він "просто працює" (тм).
Адріус

Відповіді:


255

Мабуть, у Go 1.3 http.Client є поле Timeout

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

Це зробив для мене трюк.


10
Ну, це мені досить добре. Радий, що я трохи прокрутився вниз :)
Джеймс Адам

5
Чи можливий різний час очікування на запит?
Арно Рінькін

11
Що відбувається, коли тайм-аут потрапив? Чи Getповертається помилка? Я трохи розгублений, тому що Годок Clientговорить: Таймер залишається запущеним після повернення Get, Head, Post або Do і перерве читання Response.Body. Так що це означає , що або Get або читання Response.Bodyможе бути перерваний з -за помилки?
Аві Льон

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

яка перевага використання net.DialTimeout над Transport.ResponseHeaderTimeout, описаного у відповіді zzzz?
Даніеле Б

4
@Daniel B: Ви задаєте неправильне запитання. Йдеться не про переваги, а про те, що робить кожен код. DialTimeouts вмикається, якщо сервер не може бути забитий, а інші тайм-аути починаються, якщо певні операції на встановленому з'єднанні займуть занадто довго. Якщо ваші цільові сервери швидко встановлять з'єднання, але потім почнуть повільно забороняти вам, час очікування набору не допоможе.
Волкер

1
@Volker, дякую за вашу відповідь. Насправді я це зрозумів і так: схоже, що Transport.ResponseHeaderTimeout встановлює час очікування читання, тобто час очікування після встановлення з'єднання, а ваш час - час очікування набору. Рішення від dmichael стосується як таймауту набору, так і таймауту читання.
Даніеле Б

1
@Jonno: У Go немає жодних ролей. Це перетворення типів.
Волкер

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
Цей тайм-аут не працює із збереженням з'єднань, що за замовчуванням, і, як я думаю, більшість людей має використовувати. Ось що я придумав, щоб розібратися з цим: gist.github.com/seantalts/11266762
xitrium

Дякуємо @xitrium та Еріку за додатковий вклад.
dmichael

Я відчуваю, що це не так, як ви сказали, що нам потрібно буде створити нового клієнта для кожного запиту. Оскільки Dial - це функція, на яку я думаю, що вона отримує дзвінок кожного разу, коли ви надсилаєте кожен запит у одного клієнта.
A-letubby

Ви впевнені, що кожного разу вам потрібен новий клієнт? Кожен раз, коли він набирає номер, замість net.Dial, він використовуватиме функцію, яку будує TimeoutDialer. Це нове з'єднання, коли термін оцінюється щоразу, із нового часу. Тепер () дзвінок.
Блейк Колдвелл

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
Додаткова інформація: на документ, граничний термін, накладений контекстом, також включає читання органу, як і 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)
...

Нічого вище не перевіряли.


Будь ласка, будь-хто виправить мене, але схоже, що ResponseHeaderTimeout йде про час очікування читання, тобто час очікування після встановлення з'єднання. Найбільш комплексним рішенням, здається, є рішення @dmichael, оскільки воно дозволяє встановити як час очікування набору, так і час очікування читання.
Даніеле Б

http.DefaultTransport.(*http.Transport).ResponseHeaderTimeout = time.Second * 45допоможіть мені багато в тесті написання запиту на час очікування. Велике спасибі.
lee


-1
timeout := time.Duration(5 * time.Second)
transport := &http.Transport{Proxy: http.ProxyURL(proxyUrl), ResponseHeaderTimeout:timeout}

Це може допомогти, але зауважте, що ResponseHeaderTimeoutпочинається лише після встановлення з'єднання.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.