遇到的问题
最近在阅读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时期待的输出是:
然而,真实的输出却是这样的:
有没有很意外?
什么原因
那是什么原因导致的呢?
其实标准库文档有提到,只是大家一般不会注意到(比如像我),只有踩到这个“坑”了才会去研究下。
查看标准库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.Path和url.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;