获取当前请求URL的方案

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

Get scheme of the current request URL

问题

在Go语言中,你可以通过http.Request.URL.Scheme来获取当前请求URL的协议。但是根据你提供的代码,它返回的是一个空字符串。你可以尝试使用r.Header.Get("X-Forwarded-Proto")来获取协议。这个方法可以从请求头中获取协议信息。以下是修改后的代码示例:

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) {
    scheme := r.Header.Get("X-Forwarded-Proto")
    if scheme == "" {
        scheme = r.URL.Scheme
    }
    fmt.Fprintf(w, "%#v\n", scheme)
}

这样修改后,你应该能够正确获取到当前请求URL的协议了。

英文:

In Ruby/Rack, I'm able to get the scheme of the current request URL from scheme#request. However, in Go, http.Request.URL.Scheme returns an empty string:

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
}

How do I get scheme of the current request URL?

答案1

得分: 10

为了同时提供HTTP和HTTPS服务,您需要调用http.ListenAndServe()http.ListenAndServeTLS()两个函数,并使用相同的处理程序,因为如果您只使用其中一个,比如问题中的示例,那么您只会监听一个协议,http.ListenAndServe()用于HTTP,http.ListenAndServeTLS()用于HTTPS,如果您尝试使用不同的协议与服务器联系,它将无法通过。

由于HTTPS是HTTP over TLS,*http.Request具有一个TLS属性,它将返回一个包含有关此请求使用的TLS的信息的*tls.ConnectionState,因此,如果您想知道客户端如何与服务器联系,可以检查请求的TLS属性,如果请求是通过HTTPS进行的,它将不为nil,如果请求是通过HTTP进行的,则TLS属性将为nil,因为只有通过HTTPS协议才能进行TLS请求。

func handler(w http.ResponseWriter, r *http.Request) {
   if r.TLS == nil {
       // 请求是通过HTTP进行的
   } else {
       // 请求是通过HTTPS进行的
   }                 
}  

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

To serve HTTP and HTTPS you will need to call both serve functions <br>
http.ListenAndServe() and http.ListenAndServeTLS() with the same handler
because if you are using only 1 of them like the example of the question then you are only listing on 1 protocol http.ListenAndServe() for http, and http.ListenAndServeTLS() for HTTPS, if you will try to contact the server with a different protocol it will not go through,

and because HTTPS is HTTP over TLS the *http.Request has a TLS property that will give you back a *tls.ConnectionState with info about the TLS that was used on this request, then if you want to know how the client contact the server you can check on the request TLS property,
if the request were made with HTTPS it would not be nil,
if the request was made with HTTP then the TLS property will be nil,
because the only way that a request was made with TLS is with the HTTPS protocol

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(&quot;/&quot;, handler)
    go func(){ 
        log.Fatal(http.ListenAndServeTLS(&quot;:8443&quot;,&quot;localhost.crt&quot;, &quot;localhost.key&quot;, nil)) 
    }()
    log.Fatal(http.ListenAndServe(&quot;:8080&quot;, nil))
}                                                   

答案2

得分: 6

快速的grep显示,在net/http的任何地方,r.URL.Scheme都没有被设置为除了空字符串以外的任何值。个人认为,尽可能地,它应该被设置,但显然我的意见是少数派。


如果你使用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))
}

从它的注释中可以看到:

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

注意:只有在像nginx、HAProxy或Apache这样的反向代理后面使用此中间件。如果你的应用程序使用这些头部来验证请求的“可信度”,那么不会(或被配置为不)从客户端请求中剥离这些头部的反向代理,或者这些头部被“原样”接受来自远程客户端(例如,当Go不在代理后面时),可能会表现为一个漏洞。

英文:

A quick grep shows that r.URL.Scheme is never set to anything other than the empty string anywhere in net/http. Personally I think it should be, as far as possible, but apparently I have a minority opinion.


If you opened a TLS listener yourself with http.ListenAndServeTLS() then presumably you know the scheme is https already. You can use a trivial middleware handler that fills in r.URL.Scheme in this case.

func AlwaysHTTPS(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        r.URL.Scheme = &quot;https&quot;

        next.ServeHTTP(w, r)
    })
}

If you're running behind a web server, then it may pass the request protocol in a header such as X-Forwarded-Proto. In this case, you can use a handler like gorilla's handlers.ProxyHeaders() to fill in the missing fields.

An example using gorilla mux:

package main

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

    &quot;github.com/gorilla/handlers&quot;
    &quot;github.com/gorilla/mux&quot;
)

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

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

From its comments:

> 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.

答案3

得分: 5

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

更好的做法是在主要的Web服务器上配置HSTS,这样您就不必担心不安全的连接。非TLS的http几乎没有什么合法的用途。对于nginx,您可以找到这篇文章很有用。对于其他Web服务器,您也可以轻松找到配置指南。

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

英文:

Since you use ListenAndServe and not ListenAndServeTLS, your scheme can be safely assumed as http. If you use both tls and non tls versions, you can use r.TLS and check it for null to know whether TLS was established. If your go app is running behind a reverse proxy, then you have to check the documentation on the web server which is forwarding the requests to your app, to learn how to configure it to pass this information as headers. Here's a link describing nginx configuration which accomplishes that. You can easily find configuration guides for other webservers as well.

Better yet, configure HSTS on your main web server, so that you don't have to worry about insecure connections altogether. There are very few, if any, legitimate uses to non-TLS http. For nginx you can find this article useful. And again for other web servers you will easily find configuration guides.

If you're unsure if your site/application needs https I recommend reading this.

答案4

得分: 1

localhost是URL形成的特殊情况。如果你的客户端是localhost,那么它将为空。

net.http包文档
> 作为一个特殊情况,如果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)                    
}                                                     
英文:

localhost is a special case for URL formation. It is going to be empty anyway if your client is localhost.

net.http package 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.

The way to get required url/uri information is to get it from http.Request directly. For example:

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

答案5

得分: 1

http头部的X-Forwarded-Proto字段将包含http或https。

英文:

http header X-Forwarded-Proto will have http or https

答案6

得分: -2

这是因为你正在访问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

// Scheme可以是http/https,这取决于你的服务器处理的协议。
u.Scheme = "http"
英文:

It is because, you're accessing the HTTP server so:

GET / HTTP/1.1
Host: localhost:8080

in this cases, based on parsing what you get is raw URL from Go's http.Request.URL. why you are getting this is because you are accessing the URL from a relative path, hence the lack of a Host or Scheme in the URL object.

If you do want to get the HTTP host, you may have to access the Host attribute of the http.Request struct. See http://golang.org/pkg/http/#Request

as it is not directly available, but you can still be able to assemble it:

u := r.URL

// The scheme can be http/https because that&#39;s depends on protocol your server handles.
u.Scheme = &quot;http&quot;

huangapple
  • 本文由 发表于 2016年11月27日 15:21:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/40826664.html
匿名

发表评论

匿名网友

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

确定