如何使Go的HTTP客户端不自动跟随重定向?

huangapple go评论79阅读模式
英文:

How Can I Make the Go HTTP Client NOT Follow Redirects Automatically?

问题

我目前正在使用Go编写一些与REST API交互的软件。我尝试查询的REST API端点返回一个HTTP 302重定向以及一个指向资源URI的HTTP Location头。

我想使用我的Go脚本获取HTTP Location头以供以后处理。

以下是我目前为实现此功能所做的操作:

package main

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

var BASE_URL = "https://api.example.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)

    // 如果我们到达这里,意味着有两种情况:要么此HTTP请求实际上失败了,要么我们收到了一个HTTP重定向响应,需要处理它。
    if err != nil {
        if resp.StatusCode == 302 {
            fmt.Println("got redirect")
        } else {
            panic("HTTP request failed.")
        }
    }
    defer resp.Body.Close()

}

对我来说,这感觉有点像一个hack。通过覆盖http.ClientCheckRedirect函数,我基本上被迫将HTTP重定向视为错误(实际上它们不是错误)。

我看到其他几个地方建议使用HTTP传输而不是HTTP客户端,但我不确定如何使其工作,因为我需要HTTP客户端来使用HTTP基本身份验证与此REST API进行通信。

你们中的任何人能告诉我一种在不抛出错误和处理错误的情况下进行带有基本身份验证的HTTP请求的方法吗?

英文:

I'm currently writing some software in Go that interacts with a REST API. The REST API endpoint I'm trying to query returns an HTTP 302 redirect along with an HTTP Location header, pointing to a resource URI.

I'm trying to use my Go script to grab the HTTP Location header for later processing.

Here's what I'm currently doing to achieve this functionality:

package main

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

var BASE_URL = "https://api.example.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(EXAMPLE_API_KEY_ID, EXAMPLE_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()

}

This feels like a bit of a hack to me. By overriding the http.Client's CheckRedirect function, I'm essentially forced to treat HTTP redirects like errors (which they aren't).

I've seen several other places suggesting to use an HTTP transport instead of an HTTP client -- but I'm not sure how to make this work since I need the HTTP Client as I need to use HTTP Basic Auth to communicate with this REST API.

Can any of you tell me a way to make HTTP requests with Basic Authentication -- while not following redirects -- that doesn't involve throwing errors and error handling?

答案1

得分: 210

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

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

这样,http 包会自动知道:“啊,我不应该跟随任何重定向”,但不会抛出任何错误。根据源代码中的注释:

作为一个特殊情况,如果 CheckRedirect 返回 ErrUseLastResponse,
那么最近的响应将以其未关闭的状态返回,同时返回一个空错误。

英文:

There's a much simpler solution right now:

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

This way, the http package automatically knows: "Ah, I shouldn't follow any redirects", but does not throw any error. From the comment in the source code:

> As a special case, if CheckRedirect returns ErrUseLastResponse,
> then the most recent response is returned with its body
> unclosed, along with a nil error.

答案2

得分: 12

另一种选择是在不使用RoundTrip的情况下使用客户端本身:

// 创建一个自定义错误以了解是否发生了重定向
var RedirectAttemptedError = errors.New("redirect")

client := &http.Client{}
// 返回错误,以便客户端不会尝试重定向
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
    return RedirectAttemptedError
}
// 使用客户端进行操作...
resp, err := client.Head(urlToAccess)

// 检查是否获得了自定义错误
if urlError, ok := err.(*url.Error); ok && urlError.Err == RedirectAttemptedError {
    err = nil
}

**更新**此解决方案适用于go < 1.7
英文:

Another option, using the client itself, without the RoundTrip:

<!-- language: lang-go -->

// create a custom error to know if a redirect happened
var RedirectAttemptedError = errors.New(&quot;redirect&quot;)
 
client := &amp;http.Client{}
// return the error, so client won&#39;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 &amp;&amp; urlError.Err == RedirectAttemptedError{
		err = nil	
}

UPDATE: this solution is for go < 1.7

答案3

得分: 8

这是一个使用golang编写的示例代码,它可以实现重定向和基本身份验证。你可以将这段代码放在一个名为"redirects"的包中,并在获取所需的依赖项后运行它。

希望对你有所帮助!

英文:

It is possible, but the solution inverts the problem a little. Here's a sample written up as a golang test.

package redirects

import (
	&quot;github.com/codegangsta/martini-contrib/auth&quot;
	&quot;github.com/go-martini/martini&quot;
	&quot;net/http&quot;
	&quot;net/http/httptest&quot;
	&quot;testing&quot;
)

func TestBasicAuthRedirect(t *testing.T) {
	// Start a test server
	server := setupBasicAuthServer()
	defer server.Close()

	// Set up the HTTP request
	req, err := http.NewRequest(&quot;GET&quot;, server.URL+&quot;/redirect&quot;, nil)
	req.SetBasicAuth(&quot;username&quot;, &quot;password&quot;)
	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 &amp;&amp; resp.StatusCode != 302 {
		t.Fatal(&quot;Failed with status&quot;, resp.Status)
	}

	t.Log(resp.Header.Get(&quot;Location&quot;))
}


// Create an HTTP server that protects a URL using Basic Auth
func setupBasicAuthServer() *httptest.Server {
	m := martini.Classic()
	m.Use(auth.Basic(&quot;username&quot;, &quot;password&quot;))
	m.Get(&quot;/ping&quot;, func() string { return &quot;pong&quot; })
	m.Get(&quot;/redirect&quot;, func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, &quot;/ping&quot;, 302)
	})
	server := httptest.NewServer(m)
    return server
}

You should be able to put the above code into it's own package called "redirects" and run it after fetching the required dependencies using

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

Hope this helps!

答案4

得分: 4

使用基本身份验证进行不遵循重定向的请求,可以使用接受Request参数的RoundTrip函数。

以下是代码示例:

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

你可以通过这里查看RoundTrip函数的文档,通过这里查看Request的文档。

英文:

To make request with Basic Auth that does not follow redirect use RoundTrip function that accepts *Request

This code

package main

import (
    &quot;fmt&quot;
    &quot;io/ioutil&quot;
    &quot;net/http&quot;
    &quot;os&quot;
)

func main() {
    var DefaultTransport http.RoundTripper = &amp;http.Transport{}

    req, _ := http.NewRequest(&quot;GET&quot;, &quot;http://httpbin.org/headers&quot;, nil)
    req.SetBasicAuth(&quot;user&quot;, &quot;password&quot;)

    resp, _ := DefaultTransport.RoundTrip(req)
    defer resp.Body.Close()
    contents, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Printf(&quot;%s&quot;, err)
        os.Exit(1)
    }
    fmt.Printf(&quot;%s\n&quot;, string(contents))
}

outputs

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

答案5

得分: 1

作为顶级答案的补充,

您可以控制粒度大小

func myCheckRedirect(req *http.Request, via []*http.Request, times int) error {
	err := fmt.Errorf("重定向策略:在 %d 次后停止", times)
	if len(via) >= times {
		return err
	}
	return nil
}

...

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

参考:https://golangbyexample.com/http-no-redirect-client-golang/

英文:

As an addition of top rated answer,

You can control the particle size

func myCheckRedirect(req *http.Request, via []*http.Request, times int) error {
	err := fmt.Errorf(&quot;redirect policy: stopped after %d times&quot;, times)
	if len(via) &gt;= times {
		return err
	}
	return nil
}

...

	client := &amp;http.Client{
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return myCheckRedirect(req, via, 1)
		},
	}

ref: https://golangbyexample.com/http-no-redirect-client-golang/

huangapple
  • 本文由 发表于 2014年4月25日 23:42:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/23297520.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定