Golangでhttp.Get()リクエストのタイムアウトを設定するにはどうすればよいですか?


106

GoでURLフェッチャーを作成しています。フェッチするURLのリストがあります。http.Get()各URLにリクエストを送信し、その応答を取得します。

resp,fetch_err := http.Get(url)

Getリクエストごとにカスタムタイムアウトを設定するにはどうすればよいですか?(デフォルトの時間は非常に長いため、フェッチャーは非常に遅くなります。)フェッチャーに約40〜45秒のタイムアウトを設定してから、「要求のタイムアウト」を返し、次のURLに移動します。

どうすればこれを達成できますか?


1
この方法の方が便利だと皆さんに知らせてください(少なくとも私にとっては、ネットワークの問題がある場合、ダイヤルタイムアウトはうまく機能しません):blog.golang.org/context
Audrius

@Audriusネットワークの問題があるときにダイヤルタイムアウトが機能しない理由はありますか?私は同じことを見ていると思います。それがDialTimeoutの目的だと思いましたか?!?!
ジョーダン

@Jordan私がライブラリコードの深いところに潜らなかったので、言うのは難しい。以下に私の解決策を掲載しました。私はこれをかなり長い間プロダクションで使用していますが、これまでのところ「動作するだけ」(tm)です。
オードリウス

回答:


255

どうやらGo 1.3のhttp.Clientにはタイムアウトフィールドがあります

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

それが私のトリックです。


10
まあ、それで十分です。嬉しいです、少し下にスクロールしました:)
James Adam

5
リクエストごとに異なるタイムアウトを設定する方法はありますか?
Arnaud Rinquin

10
タイムアウトになるとどうなりますか?ないGetエラーを返しますか?Godoc forがClient言っているので少し混乱しています。Get 、Head、Post、またはDoが戻った後もタイマーは実行中のままで、Response.Bodyの読み取りを中断します。だから、ということを意味しないのいずれか Get 、または読み取りがResponse.Bodyエラーによって中断することができますか?
Avi Flax 2015

1
質問、違い何http.Client.Timeout対はhttp.Transport.ResponseHeaderTimeout
ロイ・リー

2
@Royleeドキュメントによる主な違いの1つ:http.Client.Timeout応答本文を読み取る時間が含まれますが、含まれhttp.Transport.ResponseHeaderTimeoutません。
2017年

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 2013年

zzzzの回答で説明されているTransport.ResponseHeaderTimeoutよりもnet.DialTimeoutを使用する利点は何ですか?
Daniele B

4
@ダニエルB:あなたは間違った質問をしています。それは利点についてではなく、各コードが何をするかについてです。確立された接続での特定の操作に時間がかかりすぎる場合に他のタイムアウトが発生する一方で、サーバーに接続できない場合、DialTimeoutはジャンプインします。ターゲットサーバーがすばやく接続を確立した後、スロー禁止を開始した場合、ダイヤルタイムアウトは役に立ちません。
Volker

1
@Volker、回答ありがとうございます。実際に私もそれを認識しました。Transport.ResponseHeaderTimeoutは読み取りタイムアウト、つまり接続が確立された後のタイムアウトを設定し、ダイヤルタイムアウトを設定するように見えます。dmichaelによるソリューションは、ダイヤルタイムアウトと読み取りタイムアウトの両方を扱います。
Daniele B

1
@ジョンノ:囲碁にはキャストがいません。これらは型変換です。
Volker 2013年

31

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


conn.SetDeadlineの戻り値を確認しませんか?
Eric Urban

3
このタイムアウトは、キープアライブ接続では機能しません。これはデフォルトであり、ほとんどの人が使用しているはずです。これに対処するために私が思いついたのは次のとおり
seantalts

追加の入力について@xitriumとEricに感謝します。
dmichael 2014年

リクエストごとに新しいクライアントを作成する必要があるとおっしゃっていたようではありません。Dialは、毎回同じクライアントでリクエストを送信するたびに呼び出される機能だと思います。
A-letubby 2014

毎回新しいクライアントが必要ですか?ダイヤルするたびに、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
追加情報:ドキュメントごとに、コンテキストによって課される期限には、と同様に本文の読み取りも含まれhttp.Client.Timeoutます。
-kubanczyk

1
あるべき受け入れられゴー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によるソリューションのようです。
Daniele B

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、接続が確立された後にのみ開始されることに注意してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.