HTTP客户端在状态码为301且没有Location头部时不返回响应。

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

HTTP Client doesn't return response when status is 301 without a Location header

问题

当我使用Go的net/http/client包发送请求时,如果服务器响应的状态码为301或302,但没有提供Location头,则会得到一个错误对象,而没有响应对象。示例ReplIt会话:<https://replit.com/@sharat87/RedirectWithoutLocationGo#main.go>。为了防止该会话在将来消失,我在这里包含了源代码:

package main

import (
  "log"
	"net/http"
)

func main() {
	client := &http.Client{}
	resp, err := client.Get("https://httpbun.com/status/301")
	if err != nil {
		log.Println("Error getting", err)
	} else {
		log.Println(resp.Status)
	}
}

这会打印错误消息:Error getting Get "https://httpbun.com/status/301": 301 response missing Location header

然而,301或302状态响应中的Location头是可选的。服务器不需要提供它,客户端也不必重定向到它。实际上,在我的情况下,我需要查看其他头部以及响应体来获取我需要的信息。但是当Location头缺失时,Go只会返回一个错误并丢弃整个响应。例如,curl在这种情况下不会抛出错误,并且实际上会响应其他头部:

$ curl -v https://httpbun.com/status/301
[... TLS logs redacted]
> GET /status/301 HTTP/1.1
> Host: httpbun.com
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Server: nginx/1.18.0 (Ubuntu)
< Date: Sun, 25 Jul 2021 02:18:53 GMT
< Content-Type: text/plain; charset=utf-8
< Content-Length: 21
< Connection: keep-alive
< X-Powered-By: httpbun
<

我深入研究了net/http/client模块的源代码,试图了解可能发生的情况。错误消息来自于源代码中的这一行。在这里,对于重定向的请求,我们首先检查是否有Location头,如果没有,就返回一个错误。这非常限制,特别是因为规范没有将其描述为错误条件,实际上,还列出了一个合理的原因(即服务器不知道要重定向到哪里)(参考,链接到来源)。

所以,我的问题是,这是有意的吗?我有什么遗漏的地方吗?是否欢迎提出更改的PR?(在这里提问,因为看起来GitHub上的Golang存储库问题是为提案保留的?)。

我希望能得到任何建议,因为我在这里实际上是被阻塞了。

编辑:在https://github.com/golang/go/issues/49281中修复。

英文:

When I make a request with Go's net/http/client package, and the server responds with 301 or 302 status code, but doesn't provide a Location header, we get an error object, and no response object. Example ReplIt session: <https://replit.com/@sharat87/RedirectWithoutLocationGo#main.go>. Including source here in case that session goes away in the future:

package main

import (
  &quot;log&quot;
	&quot;net/http&quot;
)

func main() {
	client := &amp;http.Client{}
	resp, err := client.Get(&quot;https://httpbun.com/status/301&quot;)
	if err != nil {
		log.Println(&quot;Error getting&quot;, err)
	} else {
		log.Println(resp.Status)
	}
}

This prints the error message: Error getting Get &quot;https://httpbun.com/status/301&quot;: 301 response missing Location header.

However, the Location header in 301 or 302 status responses is optional. Servers are not required to provide it, and clients are not obliged to redirect to it. In fact, in my case, I need to look at the other headers as well as the response body for what I need. But when the Location header is missing, Go just returns an error and discards the whole response. For example, curl doesn't throw an error in this case, and actually responds with the other headers:

$ curl -v https://httpbun.com/status/301
[... TLS logs redacted]
&gt; GET /status/301 HTTP/1.1
&gt; Host: httpbun.com
&gt; User-Agent: curl/7.64.1
&gt; Accept: */*
&gt;
&lt; HTTP/1.1 301 Moved Permanently
&lt; Server: nginx/1.18.0 (Ubuntu)
&lt; Date: Sun, 25 Jul 2021 02:18:53 GMT
&lt; Content-Type: text/plain; charset=utf-8
&lt; Content-Length: 21
&lt; Connection: keep-alive
&lt; X-Powered-By: httpbun
&lt;

I dug into the source for the net/http/client module to try and see what might be happening. The error message comes up from this line in the source. Here, for the redirected request, we first check if a Location header is available, and if not, we are returning an error. This is very limiting especially because the spec doesn't describe this to be an error condition and in fact, lists a legitimate reason for doing this (i.e., the server doesn't know where to redirect to) (reference, links to sources).

So, my question is, is this intended? Am I missing something here? Would a PR to change this be invited? (Asking here because looks like Golang repo issues on GitHub are reserved for proposals?).

I'd love any advise here since I'm essentially blocked here.

Edit: Fixed in https://github.com/golang/go/issues/49281.

答案1

得分: 2

创建一个用于修复客户端响应的传输包装器。以下示例在缺少头部时设置位置头部。另一个选项是更改状态码。

type locFix struct {
    http.RoundTripper
}

func (lf locFix) RoundTrip(req *http.Request) (*http.Response, error) {
    resp, err := lf.RoundTripper.RoundTrip(req)
    if err != nil {
        return resp, err
    }
    if resp.StatusCode == 301 || resp.StatusCode == 302 {
        if resp.Header.Get("Location") == "" {
            resp.Header.Set("Location", "http://example.com")
        }
    }
    return resp, err
}

使用带有包装传输的http.Client。以下是如何为net/http默认客户端创建一个包装器的示例:

client := *http.DefaultClient
t := client.Transport
if t == nil {
    t = http.DefaultTransport
}
client.Transport = locFix{t}

resp, err = client.Get("https://httpbun.com/status/301")
英文:

Create a transport wrapper that fixes up the response for the client. The following example sets the location header when the header is missing. Another option is to change the status code.

type locFix struct {
	http.RoundTripper
}

func (lf locFix) RoundTrip(req *http.Request) (*http.Response, error) {
	resp, err := lf.RoundTripper.RoundTrip(req)
	if err != nil {
		return resp, err
	}	
	if resp.StatusCode == 301 || resp.StatusCode == 302 {
		if resp.Header.Get(&quot;Location&quot;) == &quot;&quot; {		
			resp.Header.Set(&quot;Location&quot;, &quot;http://example.com&quot;)
		}
	}
	return resp, err
}

Use a http.Client with the wrapped transport. Here’s how to create a wrapper for the net/http default client:

client := *http.DefaultClient
t := client.Transport
if t == nil {
	t = http.DefaultTransport
}
client.Transport = locFix{t}

resp, err = client.Get(&quot;https://httpbun.com/status/301&quot;)

答案2

得分: 1

默认客户端会跟随重定向。你可以通过直接使用RoundTrip来绕过这一点:

package main

import (
   "fmt"
   "net/http"
)

func main() {
   req, err := http.NewRequest("GET", "http://httpbun.com/status/301", nil)
   if err != nil {
      panic(err)
   }
   res, err := new(http.Transport).RoundTrip(req)
   if err != nil {
      panic(err)
   }
   defer res.Body.Close()
   fmt.Printf("%+v\n", res)
}
英文:

The default client follows redirects. You can bypass this by using
RoundTrip directly:

package main

import (
   &quot;fmt&quot;
   &quot;net/http&quot;
)

func main() {
   req, err := http.NewRequest(&quot;GET&quot;, &quot;http://httpbun.com/status/301&quot;, nil)
   if err != nil {
      panic(err)
   }
   res, err := new(http.Transport).RoundTrip(req)
   if err != nil {
      panic(err)
   }
   defer res.Body.Close()
   fmt.Printf(&quot;%+v\n&quot;, res)
}

huangapple
  • 本文由 发表于 2021年7月25日 10:22:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/68515077.html
匿名

发表评论

匿名网友

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

确定