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

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

resp,fetch_err := http.Get(url)

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

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

Ответ 1

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

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

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

Ответ 2

Вам нужно настроить Client свой собственный Transport, который использует пользовательская функция набора, которая обтекает DialTimeout.

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

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")
}

Ответ 3

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

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()

Ответ 4

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

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

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

resp, _ := http.DefaultClient.Do(req.WithContext(ctx))

Ответ 5

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

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)
...

Ничего выше не было проверено.

Ответ 6

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

Ответ 7

В конце я закончил использование этой функции утилиты для всех запросов, требующих тайм-аута. По некоторым причинам код @sparrovv действовал.

// reqType is one of HTTP request strings (GET, POST, PUT, DELETE, etc.)
func DoRequest(reqType string, url string, headers map[string]string, data []byte, timeoutSeconds int) (int, []byte, map[string][]string, error) {
    var reader io.Reader
    if data != nil && len(data) > 0 {
        reader = bytes.NewReader(data)
    }

    req, err := http.NewRequest(reqType, url, reader)
    if err != nil {
        return 0, nil, nil, err
    }

    // I strongly advise setting user agent as some servers ignore request without it
    req.Header.Set("User-Agent", "YourUserAgentString")
    if headers != nil {
        for k, v := range headers {
            req.Header.Set(k, v)
        }
    }

    var (
        statusCode int
        body       []byte
        timeout    time.Duration
        ctx        context.Context
        cancel     context.CancelFunc
        header     map[string][]string
    )
    timeout = time.Duration(time.Duration(timeoutSeconds) * time.Second)
    ctx, cancel = context.WithTimeout(context.Background(), timeout)
    defer cancel()
    err = httpDo(ctx, req, func(resp *http.Response, err error) error {
        if err != nil {
            return err
        }

        defer resp.Body.Close()
        body, _ = ioutil.ReadAll(resp.Body)
        statusCode = resp.StatusCode
        header = resp.Header

        return nil
    })

    return statusCode, body, header, err
}

Ответ 8

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

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