【问题标题】:Get scheme of the current request URL获取当前请求 URL 的 scheme
【发布时间】:2017-04-11 03:13:14
【问题描述】:

在 Ruby/Rack 中,我可以get the scheme of the current request URL from scheme#request。然而,在 Go 中,http.Request.URL.Scheme 返回一个空字符串:

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\n", r.URL.Scheme) // Always shows empty string
}

如何获取当前请求 URL 的 scheme?

【问题讨论】:

  • 你可以检查r.Proto它会根据文档返回HTTP/1.1或HTTP/2,但不确定它是否会改变HTTPS
  • @YandryPozo r.Proto 只有在 Go 应用程序终止 TLS 本身时才会是 HTTP/2,即使用 http.ListenAndServeTLS()
  • 由于您使用 ListenAndServe 而不是 ListenAndServeTLS,因此您的方案可以安全地假定为 http。如果同时使用 tls 和非 tls 版本,则可以使用 r.TLS 并检查它是否为 null 以了解是否建立了 TLS。如果您的 Go 应用程序在反向代理后面运行,则几乎无法知道该方案是什么。
  • @MichaelKruglos 你的答案确实是这里唯一真正有效的答案,考虑到许多应用程序现在运行在 Web 服务(如 nginx)后面,在给定的环境中可能会或可能不会终止应用程序的 TLS。

标签: http go url url-scheme


【解决方案1】:

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

【讨论】:

  • 虽然这是真的,但它不是浏览器标头,它是代理标头,并且仅在为配置选择默认值时通过代理或负载平衡器时才会出现。一个不需要使用header,它是可选的。
【解决方案2】:

为了服务 httphttps,您需要使用相同的处理程序调用两个服务函数
http.ListenAndServe()http.ListenAndServeTLS() 因为如果您只使用其中的 1 个,例如问题示例,那么您只列出 1 个协议 http.ListenAndServe() 用于 httphttp.ListenAndServeTLS() 用于 https,如果您将尝试使用它不会通过的不同协议联系服务器,

因为 httpsHTTP over TLS *http.Request 有一个 TLS 属性,它将为您提供一个 *tls.ConnectionState 以及有关此请求中使用的 TLS 的信息,然后如果您想知道客户端如何联系您的服务器您可以检查请求TLS 属性, 如果请求是使用 https 发出的,则它不会为零, 如果请求是使用 http 发出的,那么TLS 属性将为nil, 因为使用 TLS 发出请求的唯一方法是使用 https 协议

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

【讨论】:

  • 虽然此代码可能会为问题提供解决方案,但最好添加有关其工作原理/方式的上下文。这可以帮助未来的用户学习并将这些知识应用到他们自己的代码中。在解释代码时,您也可能会以赞成票的形式从用户那里获得积极的反馈。
【解决方案3】:

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

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

如果您不确定您的网站/应用程序是否需要 https,我建议您阅读 this

【讨论】:

    【解决方案4】:

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


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

    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。在这种情况下,您可以使用 gorilla 的 handlers.ProxyHeaders() 之类的处理程序来填写缺失的字段。

    使用 gorilla mux 的示例:

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

    来自它的 cmets:

    ProxyHeaders 检查常见的反向代理标头并在 HTTP 请求结构中设置相应的字段。这些是用于远程(客户端)IP 地址的 X-Forwarded-For 和 X-Real-IP,用于方案 (http|https) 的 X-Forwarded-Proto 或 X-Forwarded-Scheme 以及 RFC7239 Forwarded 标头,其中可能包括客户端 IP 和方案。

    注意:此中间件仅应在 nginx、HAProxy 或 Apache 等反向代理后面使用。不(或配置为不)从客户端请求中去除这些标头的反向代理,或者这些标头从远程客户端“按原样”接受的情况下(例如,当 Go 不在代理后面时),可以表现为漏洞如果您的应用程序使用这些标头来验证请求的“可信度”。

    【讨论】:

      【解决方案5】:

      localhost 是URL 形成的特例。如果您的客户端是本地主机,它无论如何都会是空的。

      net.http package doc:

      作为一种特殊情况,如果 req.URL.Host 是“localhost”(带或不带端口号),则返回 nil URL 和 nil 错误。

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

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

      【讨论】:

      • 这是真的,但他要求的是 URL Scheme,而不是 Host。
      【解决方案6】:

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

      GET / HTTP/1.1
      Host: localhost:8080
      

      在这种情况下,基于解析你得到的是来自 Go 的 http.Request.URL 的原始 URL。为什么你得到这个是因为你是从相对路径访问 URL,因此 URL 对象中缺少 Host 或 Scheme。

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

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

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

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-04-01
      • 1970-01-01
      • 2021-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-19
      相关资源
      最近更新 更多