google-app-enginegohttp-headersgoogle-cloud-platformgoogle-app-engine-go

How do I get output from request.RemoteAddr and X-AppEngine-Country, Region, etc. in Google App Engine Standard Env for Go?


I have a service running in the Google App Engine Standard Environment written in Go that is configured to use the latest runtime at deployment (api_version: go1 - which is currently Go 1.8).

In this service I inspect the request headers for various purposes.

func extractHeaders(res http.ResponseWriter, req *http.Request) {
    ctx := appengine.NewContext(req)
    clientIPAddress, _, _ := net.SplitHostPort(req.RemoteAddr) // Output is blank
    country := req.Header.Get("X-AppEngine-Country") // Output: US
    region := req.Header.Get("X-AppEngine-Region") // Output: ?
    city := req.Header.Get("X-AppEngine-City") // Output: ?
    cityLatLong := req.Header.Get("X-AppEngine-CityLatLong") // Output 0.000000,0.000000
    ...
}

As seen in the inline comment for the lines beginning where I read the RemoteAddr field, I am not getting the output I expected per the AppEngine Standard documentation found here (How Requests are Handled | App Engine standard environment for Go | Google Cloud Platform).

While the documentation states the that X-AppEngine-* may not always be able to be filled in based on the IP address of the client request, I am never seeing them filled in with data other than that which I listed.

Is there any configuration required to get these headers (and the RemoteAddr field) populated in the App Engine Standard Environment for Go? Am I simply misunderstanding the documentation?


Solution

  • One thing to note is that req.RemoteAddr is going to end up with the IP address of the last proxy in the chain to your app. On App Engine that's probably going to end up being 127.0.0.1. net.SplitHostPort is likely erroring since the IP may not actually have a Port number.

    The Region, City, and CityLatLong data are going to come from your IP, if that information is available, it may not be as it's not strictly required. What you can try is to pull the IP address from the X-Forwarded-For header. This will typically give you the full list of proxies and the originating IP address (IPv4 or IPv6, see example results below).

    If you were to use: package hello

    import (
        "fmt"
        "net"
        "net/http"
    )
    
    func init() {
        http.HandleFunc("/", handler)
    }
    
    func handler(w http.ResponseWriter, r *http.Request) {
        clientIPAddress, _, err := net.SplitHostPort(r.RemoteAddr)
        if err != nil {
            clientIPAddress = err.Error()
        }
        rAddr := r.RemoteAddr
        forward := r.Header.Get("x-forwarded-for")
        country := r.Header.Get("X-AppEngineCountry")
        region := r.Header.Get("X-AppEngine-Region")
        city := r.Header.Get("X-AppEngine-City")
        cityLatLong := r.Header.Get("X-AppEngine-CityLatLong")
        fullHeader := r.Header
        fmt.Fprintf(w, "Hello, world!\n %v\n %v\n %v\n %v\n %v\n %v\n %v\n %v",
            clientIPAddress,
            rAddr,
            forward,
            country,
            region,
            city,
            cityLatLong,
            fullHeader
        )
    }
    

    You should see something similar to (as long as Region City, CityLatLong are avaliable otherwise those will be blank):

    Hello, world!
     missing port in address 127.0.0.1
     127.0.0.1
     2601:1c2:600:17b0::::, 2607:f8b0:400a:::2014
     ZZ
     or
     portland
     45.523452,-122.676207
     map[Accept-Encoding:[identity] X-Appengine-Default-Version-Hostname:
    [127.0.0.1:8080] X-Appengine-User-Organization:[] X-Appengine-Citylatlong:
    [45.523452,-122.676207] X-Appengine-Request-Log-Id:
    [48f03baada92bbb3872a8b3a5baa3f8b0dfff413f64fbcfa] X-
    Appengine-City:[portland] X-Forwarded-Proto:[https] X-Appengine-Server-Name:
    [127.0.0.1] Via:[1.1 google] X-Appengine-Server-Software:[Development/2.0] 
    X-Appengine-User-Id:[] X-Forwarded-For:
    [2601:1c2:600:17b0:::: 2607:f8b0:400a:::2014] X-Goog-
    Cloud-Shell-Target-Host:[127.0.0.1] X-Appengine-Country:[ZZ] Accept:
    etc
    etc
    

    Hope that helps.