首页 文章

如何使Go HTTP客户端不自动关注重定向?

提问于
浏览
36

我目前正在Go中编写一些与REST API交互的软件 . 我正在尝试查询的REST API endpoints 返回HTTP 302重定向以及指向资源URI的HTTP Location标头 .

我正在尝试使用Go脚本来获取HTTP Location标头以供以后处理 .

以下是我目前正在实现的功能:

package main

import (
        "errors"
        "fmt"
        "io/ioutil"
        "net/http"
)

var BASE_URL = "https://api.stormpath.com/v1"
var STORMPATH_API_KEY_ID = "xxx"
var STORMPATH_API_KEY_SECRET = "xxx"

func noRedirect(req *http.Request, via []*http.Request) error {
        return errors.New("Don't redirect!")
}

func main() {

        client := &http.Client{
            CheckRedirect: noRedirect
        }
        req, err := http.NewRequest("GET", BASE_URL+"/tenants/current", nil)
        req.SetBasicAuth(STORMPATH_API_KEY_ID, STORMPATH_API_KEY_SECRET)

        resp, err := client.Do(req)

        // If we get here, it means one of two things: either this http request
        // actually failed, or we got an http redirect response, and should process it.
        if err != nil {
            if resp.StatusCode == 302 {
                fmt.Println("got redirect")
            } else {
                panic("HTTP request failed.")
            }
        }
        defer resp.Body.Close()

}

这对我来说有点像黑客 . 通过覆盖 http.ClientCheckRedirect 函数,我'm essentially forced to treat HTTP redirects like errors (which they aren' t) .

我已经看到其他几个地方建议使用HTTP传输而不是HTTP客户端 - 但我不知道如何使这项工作,因为我需要HTTP客户端,因为我需要使用HTTP Basic Auth与此REST通信API .

您是否可以告诉我一种使用基本身份验证发出HTTP请求的方法 - 虽然不遵循重定向 - 但这不涉及抛出错误和错误处理?

谢谢 .

4 回答

  • 4

    要使用不遵循重定向的Basic Auth发出请求,请使用接受* RequestRoundTrip函数

    这段代码

    package main
    
    import (
        "fmt"
        "io/ioutil"
        "net/http"
        "os"
    )
    
    func main() {
        var DefaultTransport http.RoundTripper = &http.Transport{}
    
        req, _ := http.NewRequest("GET", "http://httpbin.org/headers", nil)
        req.SetBasicAuth("user", "password")
    
        resp, _ := DefaultTransport.RoundTrip(req)
        defer resp.Body.Close()
        contents, err := ioutil.ReadAll(resp.Body)
        if err != nil {
            fmt.Printf("%s", err)
            os.Exit(1)
        }
        fmt.Printf("%s\n", string(contents))
    }
    

    输出

    {
      "headers": {
        "Accept-Encoding": "gzip", 
        "Authorization": "Basic dXNlcjpwYXNzd29yZA==", 
        "Connection": "close", 
        "Host": "httpbin.org", 
        "User-Agent": "Go 1.1 package http", 
        "X-Request-Id": "45b512f1-22e9-4e49-8acb-2f017e0a4e35"
      }
    }
    
  • 11

    现在有一个更简单的解决方案:

    client: &http.Client{
        CheckRedirect: func(req *http.Request, via []*http.Request) error {
            return http.ErrUseLastResponse
        },
    }
    

    这样, http 包自动知道:"Ah, I shouldn't follow any redirects",但不会抛出任何错误 . 从源代码中的注释:

    作为一种特殊情况,如果CheckRedirect返回ErrUseLastResponse,则返回最近的响应,其主体未闭合,并返回nil错误 .

  • 77

    这是可能的,但解决方案稍微扭转了问题 . 这是一个写成golang测试的示例 .

    package redirects
    
    import (
        "github.com/codegangsta/martini-contrib/auth"
        "github.com/go-martini/martini"
        "net/http"
        "net/http/httptest"
        "testing"
    )
    
    func TestBasicAuthRedirect(t *testing.T) {
        // Start a test server
        server := setupBasicAuthServer()
        defer server.Close()
    
        // Set up the HTTP request
        req, err := http.NewRequest("GET", server.URL+"/redirect", nil)
        req.SetBasicAuth("username", "password")
        if err != nil {
            t.Fatal(err)
        }
    
        transport := http.Transport{}
        resp, err := transport.RoundTrip(req)
        if err != nil {
            t.Fatal(err)
        }
        // Check if you received the status codes you expect. There may
        // status codes other than 200 which are acceptable.
        if resp.StatusCode != 200 && resp.StatusCode != 302 {
            t.Fatal("Failed with status", resp.Status)
        }
    
        t.Log(resp.Header.Get("Location"))
    }
    
    
    // Create an HTTP server that protects a URL using Basic Auth
    func setupBasicAuthServer() *httptest.Server {
        m := martini.Classic()
        m.Use(auth.Basic("username", "password"))
        m.Get("/ping", func() string { return "pong" })
        m.Get("/redirect", func(w http.ResponseWriter, r *http.Request) {
            http.Redirect(w, r, "/ping", 302)
        })
        server := httptest.NewServer(m)
        return server
    }
    

    您应该能够将上面的代码放入它自己的名为“redirects”的包中,并在使用后获取所需的依赖项后运行它

    mkdir redirects
    cd redirects
    # Add the above code to a file with an _test.go suffix
    go get github.com/codegangsta/martini-contrib/auth
    go get github.com/go-martini/martini
    go test -v
    

    希望这可以帮助!

  • 6

    另一种选择,使用客户端本身,没有RoundTrip:

    // create a custom error to know if a redirect happened
    var RedirectAttemptedError = errors.New("redirect")
    
    client := &http.Client{}
    // return the error, so client won't attempt redirects
    client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
            return RedirectAttemptedError
    }
    // Work with the client...
    resp, err := client.Head(urlToAccess)
    
    // test if we got the custom error
    if urlError, ok := err.(*url.Error); ok && urlError.Err == RedirectAttemptedError{
            err = nil   
    }
    

    UPDATE: 此解决方案适用于<1.7

相关问题