简体   繁体   中英

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

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?

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM