Wherefore art thou X-Forwarded-For?

Apparently the client IP can be sent several different ways, so it was necessary to check various headers. Also along these lines, none of these headers are sent when browsing locally via heroku local, meaning the only way I could test that this was working properly was “in production.” Decidedly not ideal.

Here’s the full text of my function to fetch the IP address:

func getIP(headers http.Header) *string {
	possibleKeys := []string{
		"Remote_Addr",
		"Client-IP",
		"X-Forwarded-For",
	}

	for _, key := range possibleKeys {
		if ip := headers.Get(key); ip != "" {
			comma := strings.Index(ip, ",")
			if comma != -1 {
				ip = ip[:comma]
			}
			return &ip
		}
	}

	return nil
}

Given a HTTP header object, this will check the various possible keys, returning the value if the header ccontains that key. There’s also a small check for a comma that will ensure only 1 address is returned.

Yes, I’m Pointing at You

Of some note is that this function returns *string rather than string. That is, it returns a pointer to a string rather than a string. This is also why return &ip is written the way it is. It returns not the string value contained in ip but the address where this value is stored.

The reason for this is so that if the IP is not found for some reason, this function can return nil (aka null) rather than some specific string value. My Response object accordingly holds pointers to strings, and when it is Marshaled into JSON a nil value in the IP field will be correctly returned as "ipaddress": null.

Requirement: Clarity

It wasn’t entirely clear, from the user stories given, exactly what “operating system” data is desired. I made a choice based on the structure of the User-Agent header, but it could be different. The same is true for my choice to strip out extra IP addresses.

Room for Improvement

  • Find a way to test client IP when working with a local deployment.
  • Possible: display the name of a language instead of a locale string. ie, “English” instead of “en-US”.
  • Add other interesting headers to the JSON response.
  • Add some unit tests, by manually constructing an HTTP header and ensuring that the returned values are accurate.