从 http.Request 获取客户端 IP 地址的正确方法

新手上路,请多包涵

http.Request 获取所有客户端 IP 地址的正确方法是什么?在 PHP 中有很多我应该检查的 变量。 Go 也一样吗?

我发现的一个是:

 req.RemoteAddr

请求是否区分大小写?例如 x-forwarded-for 与 --- X-Forwarded-ForX-FORWARDED-FOR 相同? (来自 req.Header.Get("X-FORWARDED-FOR")

原文由 Kokizzu 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.5k
2 个回答

查看 http.Request 可以找到以下成员变量:

 // HTTP defines that header names are case-insensitive.
// The request parser implements this by canonicalizing the
// name, making the first character and any characters
// following a hyphen uppercase and the rest lowercase.
//
// For client requests certain headers are automatically
// added and may override values in Header.
//
// See the documentation for the Request.Write method.
Header Header

// RemoteAddr allows HTTP servers and other software to record
// the network address that sent the request, usually for
// logging. This field is not filled in by ReadRequest and
// has no defined format. The HTTP server in this package
// sets RemoteAddr to an "IP:port" address before invoking a
// handler.
// This field is ignored by the HTTP client.
RemoteAddr string

您可以使用 RemoteAddr 获取远程客户端的 IP 地址和端口(格式为“IP:port”),这是原始请求者 或最后一个代理 的地址(例如负载均衡器在你的服务器前面)。

这就是您所拥有的一切。

然后您可以调查 不区分大小写 的标头(根据上面的文档),这意味着您的所有示例都将起作用并产生相同的结果:

 req.Header.Get("X-Forwarded-For") // capitalisation
req.Header.Get("x-forwarded-for") // doesn't
req.Header.Get("X-FORWARDED-FOR") // matter

这是因为在内部 http.Header.Get 将为您规范化密钥。 (如果您想直接访问标头映射,而不是通过 Get ,则需要先使用 http.CanonicalHeaderKey 。)

最后, "X-Forwarded-For" 可能是您想要查看的字段,以便获取有关客户端 IP 的更多信息。不过,这在很大程度上取决于远程端使用的 HTTP 软件,因为客户端可以根据需要在其中放置任何内容。另外,请注意此字段的 预期 格式 是逗号+空格分隔的 IP 地址列表。您需要稍微解析它以获得您选择的单个 IP(可能是列表中的第一个),例如:

 // Assuming format is as expected
ips := strings.Split("10.0.0.1, 10.0.0.2, 10.0.0.3", ", ")
for _, ip := range ips {
    fmt.Println(ip)
}

将产生:

 10.0.0.1
10.0.0.2
10.0.0.3

原文由 tomasz 发布,翻译遵循 CC BY-SA 3.0 许可协议

这是一个完全有效的例子

package main

import (
    // Standard library packages
    "fmt"
    "strconv"
    "log"
    "net"
    "net/http"

    // Third party packages
    "github.com/julienschmidt/httprouter"
    "github.com/skratchdot/open-golang/open"
)

// https://blog.golang.org/context/userip/userip.go
func getIP(w http.ResponseWriter, req *http.Request, _ httprouter.Params){
    fmt.Fprintf(w, "<h1>static file server</h1><p><a href='./static'>folder</p></a>")

    ip, port, err := net.SplitHostPort(req.RemoteAddr)
    if err != nil {
        //return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)

        fmt.Fprintf(w, "userip: %q is not IP:port", req.RemoteAddr)
    }

    userIP := net.ParseIP(ip)
    if userIP == nil {
        //return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)
        fmt.Fprintf(w, "userip: %q is not IP:port", req.RemoteAddr)
        return
    }

    // This will only be defined when site is accessed via non-anonymous proxy
    // and takes precedence over RemoteAddr
    // Header.Get is case-insensitive
    forward := req.Header.Get("X-Forwarded-For")

    fmt.Fprintf(w, "<p>IP: %s</p>", ip)
    fmt.Fprintf(w, "<p>Port: %s</p>", port)
    fmt.Fprintf(w, "<p>Forwarded for: %s</p>", forward)
}

func main() {
    myport := strconv.Itoa(10002);

    // Instantiate a new router
    r := httprouter.New()

    r.GET("/ip", getIP)

    // Add a handler on /test
    r.GET("/test", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
        // Simply write some test data for now
        fmt.Fprint(w, "Welcome!\n")
    })

    l, err := net.Listen("tcp", "localhost:" + myport)
    if err != nil {
        log.Fatal(err)
    }
    // The browser can connect now because the listening socket is open.

    //err = open.Start("http://localhost:"+ myport + "/test")
    err = open.Start("http://localhost:"+ myport + "/ip")
    if err != nil {
         log.Println(err)
    }

    // Start the blocking server loop.
    log.Fatal(http.Serve(l, r))
}

原文由 Stefan Steiger 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题