遇到的问题

最近在阅读echo框架的源码,发现context.go文件在读取请求的scheme时是单独封装了个方法。就很奇怪,go语言标准库不是自带了方法吗,干嘛不用?

于是写了段代码来验证:

1
2
3
4
5
6
func main() {
	http.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Scheme:", r.URL.Scheme)
	})
	log.Fatal(http.ListenAndServe(":9000", nil))
}

当我们请求http://127.0.0.1:9000/foo时期待的输出是:

1
Scheme: http

然而,真实的输出却是这样的:

1
Scheme:

有没有很意外?

什么原因

那是什么原因导致的呢?

其实标准库文档有提到,只是大家一般不会注意到(比如像我),只有踩到这个“坑”了才会去研究下。

查看标准库http.Request注释:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
type Request struct {
	//***

	// URL specifies either the URI being requested (for server
	// requests) or the URL to access (for client requests).
	//
	// For server requests, the URL is parsed from the URI
	// supplied on the Request-Line as stored in RequestURI.  For
	// most requests, fields other than Path and RawQuery will be
	// empty. (See RFC 7230, Section 5.3)
	//
	// For client requests, the URL's Host specifies the server to
	// connect to, while the Request's Host field optionally
	// specifies the Host header value to send in the HTTP
	// request.
	URL *url.URL
	//***

注意看第二段注释,简单解释下就是,对于服务端收到的请求,http.Request只会解析请求行到url.URL,也就是说url.Pathurl.RawQuery之外的字段都是空的。

另外,在google groups也有讨论:

http.req.URI.Scheme inside is empty?

如何解决

比较推荐的做法是判断request.TLS是否为 nil,不为空即为https,为空即为http

或者,可以参考echo框架的实现来处理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
func (c *context) Scheme() string {
	// Can't use `r.Request.URL.Scheme`
	// See: https://groups.google.com/forum/#!topic/golang-nuts/pMUkBlQBDF0
	if c.IsTLS() {
		return "https"
	}
	if scheme := c.request.Header.Get("X-Forwarded-Proto"); scheme != "" {
		return scheme
	}
	if scheme := c.request.Header.Get("X-Forwarded-Protocol"); scheme != "" {
		return scheme
	}
	if ssl := c.request.Header.Get("X-Forwarded-Ssl"); ssl == "on" {
		return "https"
	}
	if scheme := c.request.Header.Get("X-Url-Scheme"); scheme != "" {
		return scheme
	}
	return "http"
}

在github issues上也有讨论,地址在这里:

net/http: Request.URL.Scheme returns an empty string. No alternative way present to get request url scheme.

小结

作为服务端接受的请求,获取Scheme需要特殊处理下;

作为客户端发送的请求,可以正常通过request.URL.Scheme取到发送请求的Scheme