关于http:获取当前请求URL的方案

Get scheme of the current request URL

在Ruby / Rack中,我可以从scheme#request获取当前请求URL的方案。但是,在Go中,http.Request.URL.Scheme返回一个空字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
   "fmt"
   "log"
   "net/http"
)

func main() {
    http.HandleFunc("/", handler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w,"%#v\
", r.URL.Scheme) // Always shows empty string
}

如何获取当前请求URL的方案?


快速grep显示r.URL.Scheme从未设置为除net/http中任何地方的空字符串以外的任何内容。我个人认为应该尽可能,但显然我有少数意见。

如果您使用http.ListenAndServeTLS()自己打开了TLS侦听器,那么大概您已经知道该方案已经是https。在这种情况下,可以使用普通的中间件处理程序来填充r.URL.Scheme

1
2
3
4
5
6
7
func AlwaysHTTPS(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        r.URL.Scheme ="https"

        next.ServeHTTP(w, r)
    })
}

如果您在Web服务器后面运行,则它可能在标头(例如X-Forwarded-Proto)中传递请求协议。在这种情况下,可以使用像大猩猩的handlers.ProxyHeaders()这样的处理程序来填写缺少的字段。

使用大猩猩复用器的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
   "log"
   "net/http"

   "github.com/gorilla/handlers"
   "github.com/gorilla/mux"
)

func main() {
    r := mux.NewRouter()
    r.Use(handlers.ProxyHeaders)

    http.Handle("/", r)
    log.Fatal(http.ListenAndServe("[::]:8009", nil))
}

来自其评论:

ProxyHeaders inspects common reverse proxy headers and sets the corresponding fields in the HTTP request struct. These are X-Forwarded-For and X-Real-IP for the remote (client) IP address, X-Forwarded-Proto or X-Forwarded-Scheme for the scheme (http|https) and the RFC7239 Forwarded header, which may include both client IPs and schemes.

NOTE: This middleware should only be used when behind a reverse proxy like nginx, HAProxy or Apache. Reverse proxies that don't (or are configured not to) strip these headers from client requests, or where these headers are accepted"as is" from a remote client (e.g. when Go is not behind a proxy), can manifest as a vulnerability if your application uses these headers for validating the 'trustworthiness' of a request.


由于使用的是ListenAndServe而不是ListenAndServeTLS,因此可以将您的方案安全地假定为http。如果同时使用tls和非tls版本,则可以使用r.TLS并将其检查为null以了解是否已建立TLS。如果go应用程序在反向代理后面运行,则必须检查将请求转发到应用程序的Web服务器上的文档,以了解如何配置它以将该信息作为标头传递。这是描述实现此目标的nginx配置的链接。您也可以轻松找到其他Web服务器的配置指南。

更好的是,在主Web服务器上配置HSTS,这样您就不必担心完全不安全的连接。非TLS http的合法使用(如果有的话)很少。对于nginx,您可以找到这篇文章有用。对于其他Web服务器,您又可以轻松找到配置指南。

如果不确定您的站点/应用程序是否需要https,建议阅读此内容。


为了提供http和https,您将需要同时调用这两个serve函数
http.ListenAndServe()http.ListenAndServeTLS()具有相同的处理程序
因为如果您只使用其中的一种(如问题示例),那么您只会在1个协议http.ListenAndServe()上列出http,在http.ListenAndServeTLS()上列出https,如果您尝试使用其他协议与服务器联系,它将不会成功通过,

并且因为https是基于TLS的HTTP,所以*http.Request具有TLS属性,该属性将为您提供*tls.ConnectionState以及有关此请求所使用的TLS的信息,然后,如果您想知道客户端联系您的服务器,您可以检查请求TLS属性,
如果请求是通过https发出的,则不会为零,
如果请求是通过http发出的,则TLS属性将为nil
因为使用TLS发出请求的唯一方法是使用https协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func handler(w http.ResponseWriter, r *http.Request) {
   if r.TLS == nil {
       // the scheme was http
   } else {
       // the scheme was https
   }                
}  

func main() {
    http.HandleFunc("/", handler)
    go func(){
        log.Fatal(http.ListenAndServeTLS(":8443","localhost.crt","localhost.key", nil))
    }()
    log.Fatal(http.ListenAndServe(":8080", nil))
}


localhost是URL形成的特殊情况。如果您的客户端是localhost,则无论如何它将为空。

net.http软件包doc:

As a special case, if req.URL.Host is"localhost" (with or without a port number), then a nil URL and nil error will be returned.

获取必需的url / uri信息的方法是直接从http.Request获取它。例如:

1
2
3
4
func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w,"%s\
", r.Host)                    
}


http标头X-Forwarded-Proto将具有http或https


这是因为您正在访问HTTP服务器,因此:

1
2
GET / HTTP/1.1
Host: localhost:8080

在这种情况下,

基于解析您从Go的http.Request.URL获得的原始URL。之所以这样,是因为您正在从相对路径访问URL,因此URL对象中缺少主机或方案。

如果确实要获取HTTP主机,则可能必须访问http.Request结构的Host属性。参见http://golang.org/pkg/http/#Request

,因为它不是直接可用的,但是您仍然可以组装它:

1
2
3
4
u := r.URL

// The scheme can be http/https because that's depends on protocol your server handles.
u.Scheme ="http"