【问题标题】:Go lang Capture Redirect urls and status codes with timeoutsGo lang 捕获重定向 url 和带有超时的状态代码
【发布时间】:2015-11-16 05:57:19
【问题描述】:

我正在尝试向给定的 url 发出请求,并捕获重定向 url 及其所遵循的状态代码。

我已经尝试为我的具体问题寻找答案 - this 接近了。

但是,我还需要在整个连接上添加代理、用户代理和超时,即无论有多少重定向/代理延迟等,时间量都不应超过 X 秒。

我通过设置请求标头来处理用户代理,并通过将其添加到传输结构来处理代理。 我尝试探索 CheckRedirect 以进行重定向 - 但这只给了我 Url,我还需要状态代码,所以我必须实现 RoundTrip 函数。

目前一切正常 - 除了超时。 这是我到目前为止所拥有的 - playground link 我也在这里粘贴了相关代码 - 游乐场有一个完整版本,带有一个模拟重定向服务器 - 不幸的是,它恐慌说连接可能由于游乐场限制而被拒绝 - 但它完全在本地工作。

type Redirect struct {
    StatusCode int
    URL string
}

type TransportWrapper struct {
    Transport http.RoundTripper
    Url string
    Proxy string
    UserAgent string
    TimeoutInSeconds int
    FinalUrl string
    RedirectUrls []Redirect
}
// Implementing Round Tripper to capture intermediate urls
func (t *TransportWrapper) RoundTrip(req *http.Request) (*http.Response, error) {
    transport := t.Transport
    if transport == nil {
        transport = http.DefaultTransport
    }

    resp, err := transport.RoundTrip(req)
    if err != nil {
        return resp, err
    }

    // Remember redirects
    if resp.StatusCode >= 300 && resp.StatusCode <= 399 {
        t.RedirectUrls = append(
            t.RedirectUrls, Redirect{resp.StatusCode, req.URL.String()},
        )
    }
    return resp, err
}

func (t *TransportWrapper) Do() (*http.Response, error) {
    t.Transport = &http.Transport{}
    if t.Proxy != "" {
        proxyUrl, err := url.Parse(t.Proxy)
        if err != nil {
            return nil, err
        }

        t.Transport = &http.Transport{Proxy:http.ProxyURL(proxyUrl)}
        // HELP
        // Why does this fail
        // t.Transport.Proxy = http.ProxyUrl(proxyUrl)
    }

    client := &http.Client{
        Transport: t, // Since I've implemented RoundTrip I can pass this
        // Timeout: t.TimeoutInSeconds * time.Second, // This Fails 
    }

    req, err := http.NewRequest("GET", t.Url, nil)
    if err != nil {
        return nil, err
    }

    if t.UserAgent != "" {
        req.Header.Set("User-Agent", t.UserAgent)
    }

    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }

    t.FinalUrl = resp.Request.URL.String()
    return resp, nil
}

func startClient() {
    t := &TransportWrapper {
        Url: "http://127.0.0.1:8080/temporary/redirect?num=5",
        // Proxy
        // UserAgent
        // Timeout
    }

    _, err := t.Do()
    if err != nil {
        panic(err)
    }

    fmt.Printf("Intermediate Urls: \n")
    for i, v := range t.RedirectUrls {
        fmt.Printf("[%d] %s\n", i, v)
    }

}

问题 1:如何添加超时?

尝试#1:

client := &http.Client{ Transport: t, Timeout: myTimeout }

但是 Go 抱怨说“*main.TransportWrapper 不支持 CancelRequest;不支持超时”

尝试 #2:

// Adding a CancelRequest
func (t *TransportWrapper) CancelRequest(req *http.Request) {
    dt := http.DefaultTransport
    dt.CancelRequest(req)
}

但是 Go 抱怨说“dt.CancelRequest undefined (type http.RoundTripper 没有字段或方法 CancelRequest)”

如何在不做太多事情的情况下实现这个 CancelRequest 并让默认的 CancelRequest 接管?

问题 2:我是否走上了一条错误的道路,是否有解决问题的替代方法,

给定一个 Url、Proxy、UserAgent 和 Timeout - 返回响应以及重定向 url 及其状态代码。

我希望我的措辞恰当。

谢谢

【问题讨论】:

    标签: http redirect go


    【解决方案1】:

    已经有一个检查重定向的钩子,Client.CheckRedirect

    你可以提供一个回调来做你想做的事。

    如果您真的想创建自己的传输来扩展其他功能,则需要提供 CancelRequest 方法,就像错误所说的处理 Client.Timeout 一样。

    func (t *TransportWrapper) CancelRequest(req *Request) {
        t.Transport.CancelRequest(req)
    }
    

    更常见的是,您会嵌入Transport,以便自动提升所有方法和字段。但是,您应该避免传输中的可写字段,因为预计它可以安全地同时使用,否则您应该使用互斥锁保护所有访问,或者您必须确保它仅在一个 goroutine 中使用。

    一个最小的例子如下:

    type TransportWrapper struct {
        *http.Transport
        RedirectUrls []Redirect
    }
    
    func (t *TransportWrapper) RoundTrip(req *http.Request) (*http.Response, error) {
        transport := t.Transport
        if transport == nil {
            transport = http.DefaultTransport.(*http.Transport)
        }
    
        resp, err := transport.RoundTrip(req)
        if err != nil {
            return resp, err
        }
    
        // Remember redirects
        if resp.StatusCode >= 300 && resp.StatusCode <= 399 {
            fmt.Println("redirected")
            t.RedirectUrls = append(
                t.RedirectUrls, Redirect{resp.StatusCode, req.URL.String()},
            )
        }
        return resp, err
    }
    

    然后你就可以在客户端使用超时了:

    client := &http.Client{
        Transport: &TransportWrapper{
            Transport: http.DefaultTransport.(*http.Transport),
        },
        Timeout: 5 * time.Second,
    }
    

    【讨论】:

    • Client.CheckRedirect 允许我捕获重定向 URL,但我也想知道它们的状态代码,即 301、302 等,我相信 this SO answer 说我需要创建自己的往返。我在这里尝试了 CancelRequest,但这并没有什么不同——我让服务器休眠 5 秒,然后超时 1 秒,但仍然没有运气。带有 sleep 和 cancelrequest 的更新代码是 here
    • @AbhishekShivanna:我不确定为什么没有代码和错误的 CancelRequest 对你不起作用,但我举了一个最小的例子。我也会避免将Do 方法放在Transport 中,因为它属于Client,并且会使代码与两者之间的循环引用混淆。
    • 非常感谢!我决定采用嵌入的方法。当您谈论并发保护时,是否因为相同的传输包装器也可能在另一个客户端中使用?我计划为我想要所有重定向的每个 URL 创建一个新的传输包装器和客户端。在这种情况下,我不应该需要锁吗?还是默认传输执行的一些内部魔法和 go 例程的锁?
    • 我让服务器休眠了 5 秒钟,然后才给出响应。这会导致 panic: runtime error: invalid memory address or nil pointer dereference 。它指向t.reqMu.Lock()line 280 of transport.go
    • 即使在我收到回复后,它似乎也在调用 CancelRequest。我已经通过在请求成功完成后运行睡眠来测试这一点。我发现这是因为我没有做resp.Body.Close()。在成功的运行中,我可以做到这一点,但是在往返中呢?我不想在那里关闭身体吗?
    猜你喜欢
    • 2019-03-29
    • 1970-01-01
    • 1970-01-01
    • 2018-09-05
    • 1970-01-01
    • 1970-01-01
    • 2021-12-16
    • 1970-01-01
    • 2017-04-03
    相关资源
    最近更新 更多