2013-06-03 51 views
70

Tôi đang tạo một trình tìm nạp URL trong Go và có danh sách các URL cần tìm nạp. Tôi gửi http.Get() yêu cầu tới từng URL và nhận phản hồi của họ.Cách đặt thời gian chờ cho các yêu cầu http.Get() ở Golang?

resp,fetch_err := http.Get(url) 

Làm cách nào để đặt thời gian chờ tùy chỉnh cho từng yêu cầu Nhận? (Thời gian mặc định là rất dài và điều đó làm cho trình tìm nạp của tôi thực sự chậm.) Tôi muốn trình tìm nạp của tôi có thời gian chờ khoảng 40-45 giây sau đó nó sẽ trả về "yêu cầu hết thời gian" và chuyển sang URL tiếp theo.

Tôi làm cách nào để đạt được điều này?

+1

Chỉ cần cho các bạn biết rằng tôi thấy cách này thuận tiện hơn (thời gian chờ quay số không hoạt động tốt nếu có sự cố mạng, ít nhất là đối với tôi): https://blog.golang.org/context – Audrius

+0

@Audrius Bất kỳ ý tưởng nào tại sao thời gian chờ quay số không hoạt động khi có sự cố mạng? Tôi nghĩ tôi cũng thấy điều tương tự. Tôi nghĩ đó là những gì DialTimeout đã cho?!?! – Jordan

+0

@Jordan Khó nói là tôi đã không đi sâu vào mã thư viện. Tôi đã đăng giải pháp của tôi dưới đây. Tôi đang sử dụng nó trong sản xuất trong một thời gian dài và cho đến nay nó "chỉ hoạt động" (tm). – Audrius

Trả lời

179

Rõ ràng tại Gò 1,3 http.Client có Timeout lĩnh vực

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

Đó là thực hiện các trick cho tôi.

+8

Vâng, đó là đủ tốt cho tôi. Vui vì tôi đã cuộn xuống một chút :) –

+2

Có cách nào để có thời gian chờ khác nhau theo yêu cầu không? –

+7

Điều gì sẽ xảy ra khi hết thời gian chờ? 'Get' có trả về lỗi không? Tôi là một chút bối rối vì Godoc cho 'Client' nói: _Các bộ đếm thời gian vẫn chạy sau khi Get, Head, Post, hoặc Do trở lại và sẽ làm gián đoạn đọc của Response.Body._ Vì vậy, điều đó có nghĩa rằng _either_' Get' _or_ đọc 'Response.Body' có thể bị gián đoạn bởi một lỗi? –

8

Một cách nhanh chóng và dơ bẩn:

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

này được biến đổi trạng thái toàn cầu w/o phối hợp bất kỳ. Tuy nhiên, nó có thể là có thể okay cho trình tìm nạp url của bạn. Nếu không, hãy tạo một cá thể riêng tư của 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) 
... 

Không có gì ở trên được thử nghiệm.

+0

Xin vui lòng bất cứ ai sửa tôi, nhưng có vẻ như ResponseHeaderTimeout là về thời gian chờ đọc, đó là thời gian chờ sau khi kết nối đã được thiết lập. Giải pháp toàn diện nhất có vẻ là giải pháp của @dmichael, vì nó cho phép đặt cả thời gian chờ quay số và thời gian chờ đọc. –

52

Bạn cần thiết lập Client của riêng mình với Transport của riêng bạn sử dụng chức năng Quay số tùy chỉnh bao quanh khoảng DialTimeout.

Giống như (hoàn toàn chưa được kiểm tra) 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") 
} 
+0

Cảm ơn rất nhiều! Đây chính là điều tôi đang tìm kiếm. – pymd

+0

lợi thế của việc sử dụng net.DialTimeout trên Transport.ResponseHeaderTimeout được mô tả bằng câu trả lời của zzzz là gì? –

+3

@Daniel B: Bạn đang đặt câu hỏi sai. Nó không phải là về lợi thế nhưng về những gì mỗi mã không. DialTimeouts nhảy vào nếu máy chủ không thể bị bỏ qua trong khi các thời gian chờ khác khởi động nếu các hoạt động nhất định trên kết nối được thiết lập mất quá nhiều thời gian. Nếu máy chủ mục tiêu của bạn thiết lập kết nối nhanh chóng nhưng sau đó bắt đầu chậm bạn, thời gian chờ quay số sẽ không giúp ích gì. – Volker

26

Để thêm vào câu trả lời Volker, nếu bạn cũng muốn đặt thời gian chờ đọc/ghi, thêm vào thời gian chờ kết nối, bạn có thể làm điều gì đó như sau đây

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

Mã này được kiểm tra và đang hoạt động trong quá trình sản xuất. Các ý chính đầy đủ với các bài kiểm tra có sẵn ở đây https://gist.github.com/dmichael/5710968

Hãy nhận biết rằng bạn sẽ cần phải tạo ra một khách hàng mới cho mỗi yêu cầu vì sự conn.SetDeadline mà tham chiếu đến một điểm nào đó trong tương lai từ time.Now()

+0

Bạn không nên kiểm tra giá trị trả về của conn.SetDeadline? –

+2

Lần hết thời gian này không hoạt động với các kết nối keepalive, đây là mặc định và hầu hết mọi người nên sử dụng những gì tôi tưởng tượng. Dưới đây là những gì tôi đã đưa ra để giải quyết vấn đề này: http://gist.github.com/seantalts/11266762 – xitrium

+0

Cảm ơn @xitrium và Eric về đầu vào bổ sung. – dmichael

0

Cuối cùng, tôi đã kết thúc bằng chức năng tiện ích này cho tất cả các yêu cầu yêu cầu hết thời gian chờ. Vì một số lý do mã @sparrovv đã hoạt động.

// 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 
}