如何找到Go http.Response的远程IP地址?

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

How do I find the remote IP address for a Go http.Response?

问题

http.Request结构体包含请求发送者的远程IP和端口:

// RemoteAddr允许HTTP服务器和其他软件记录发送请求的网络地址,通常用于日志记录。ReadRequest不会填充此字段,也没有定义的格式。此软件包中的HTTP服务器在调用处理程序之前将RemoteAddr设置为“IP:port”地址。
// HTTP客户端会忽略此字段。
RemoteAddr string

http.Response对象没有这样的字段。

我想知道响应我发送的请求的IP地址,即使我将其发送到DNS地址。

我认为net.LookupHost()可能有所帮助,但是1)它可以返回单个主机名的多个IP,2)它会忽略主机文件,除非cgo可用,而在我的情况下不可用。

是否可能检索http.Response的远程IP地址?

英文:

The http.Request struct includes the remote IP and port of the request's sender:

    // 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**

The http.Response object has no such field.

I would like to know the IP address that responded to the request I sent, even when I sent it to a DNS address.

I thought that net.LookupHost() might be helpful, but 1) it can return multiple IPs for a single host name, and 2) it ignores the hosts file unless cgo is available, which it is not in my case.

Is it possible to retrieve the remote IP address for an http.Response?

答案1

得分: 7

使用net/http/httptrace包,并使用GotConnInfo钩子来捕获net.Conn及其对应的Conn.RemoteAddr()

这将给出Transport实际拨号的地址,而不是在DNSDoneInfo中解析的地址:

package main

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

func main() {
	req, err := http.NewRequest("GET", "https://example.com/", nil)
	if err != nil {
		log.Fatal(err)
	}

	trace := &httptrace.ClientTrace{
		GotConn: func(connInfo httptrace.GotConnInfo) {
			log.Printf("resolved to: %s", connInfo.Conn.RemoteAddr())
		},
	}

	req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))

	client := &http.Client{}
	_, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
}

输出:

~ go run ip.go
2017/02/18 19:38:11 resolved to: 104.16.xx.xxx:443
英文:

Use the net/http/httptrace package and use the GotConnInfo hook to capture the net.Conn and its corresponding Conn.RemoteAddr().

This will give you the address the Transport actually dialled, as opposed to what was resolved in DNSDoneInfo:

package main

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

func main() {
	req, err := http.NewRequest("GET", "https://example.com/", nil)
	if err != nil {
		log.Fatal(err)
	}

	trace := &httptrace.ClientTrace{
		GotConn: func(connInfo httptrace.GotConnInfo) {
			log.Printf("resolved to: %s", connInfo.Conn.RemoteAddr())
		},
	}

	req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))

	client := &http.Client{}
	_, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
}

Outputs:

~ go run ip.go
2017/02/18 19:38:11 resolved to: 104.16.xx.xxx:443

答案2

得分: -1

我提出的另一个解决方案是在HTTP客户端传输中挂钩DialContext函数。这是一种特定的解决方案,允许您修改http.Client而不是请求,这可能会很有用。

首先,我们创建一个返回挂钩拨号上下文的函数

func remoteAddressDialHook(remoteAddressPtr *net.Addr) func(ctx context.Context, network string, address string) (net.Conn, error) {
	hookedDialContext := func(ctx context.Context, network, address string) (net.Conn, error) {
		originalDialer := &net.Dialer{
			Timeout:   30 * time.Second,
			KeepAlive: 30 * time.Second,
		}

		conn, err := originalDialer.DialContext(ctx, network, address)
		if err != nil {
			return nil, err
		}

		// conn成功创建
		*remoteAddressPtr = conn.RemoteAddr()
		return conn, err
	}

	return hookedDialContext
}

然后,我们可以使用此函数创建一个写入输出参数的DialContext

var remoteAddr net.Addr
customTransport := &http.Transport{
	Proxy:                 http.ProxyFromEnvironment,
	DialContext:           remoteAddressDialHook(&remoteAddr),
	ForceAttemptHTTP2:     true,
	MaxIdleConns:          100,
	IdleConnTimeout:       90 * time.Second,
	TLSHandshakeTimeout:   10 * time.Second,
	ExpectContinueTimeout: 1 * time.Second,
}
customHttpClient := http.Client{
	Transport: customTransport,
}

// 像通常使用HTTP客户端一样进行操作,它将设置remoteAddr为远程地址
fmt.Println(remoteAddr.String())
英文:

Another solution I came up with was the hook the DialContext function in the http client transport. This is a specific solution that lets you modify the http.Client instead of the request which may be useful.

We first create a function that returns a hooked dial context

func remoteAddressDialHook(remoteAddressPtr *net.Addr) func(ctx context.Context, network string, address string) (net.Conn, error) {
	hookedDialContext := func(ctx context.Context, network, address string) (net.Conn, error) {
		originalDialer := &net.Dialer{
			Timeout:   30 * time.Second,
			KeepAlive: 30 * time.Second,
		}

		conn, err := originalDialer.DialContext(ctx, network, address)
		if err != nil {
			return nil, err
		}

		// conn was successfully created
		*remoteAddressPtr = conn.RemoteAddr()
		return conn, err
	}

	return hookedDialContext
}

We can then use this function to create a DialContext that writes to an outparameter

	var remoteAddr net.Addr
	customTransport := &http.Transport{
		Proxy:                 http.ProxyFromEnvironment,
		DialContext:           remoteAddressDialHook(&remoteAddr),
		ForceAttemptHTTP2:     true,
		MaxIdleConns:          100,
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
	}
	customHttpClient := http.Client{
		Transport: customTransport,
	}

    // do what you normally would with a http client, it will then set the remoteAddr to be the remote address
    fmt.Println(remoteAddr.String())

huangapple
  • 本文由 发表于 2017年2月18日 03:07:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/42305636.html
匿名

发表评论

匿名网友

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

确定