Documentation
¶
Overview ¶
Package forwarded parses the X-Forwarded-For header along with a socket remote address to produce the chain of addresses that a request traversed. It is a Go port of the npm module "forwarded" (the same helper that Express and the "proxy-addr" module build on) and reproduces that module's ordering and trimming rules exactly.
Use this package when your application sits behind one or more reverse proxies or load balancers and you need to recover the client's original address, or the full list of intermediaries a request passed through. The socket's immediate peer (the nearest proxy, or the client itself when there is no proxy) is always known reliably; the X-Forwarded-For header records the addresses each proxy observed and appended as the request was relayed onward.
The algorithm is deliberately simple and does no validation. Forwarded takes the socket remote address and the raw X-Forwarded-For value, strips any port from the remote address, and returns a slice whose first element is that remote address. It then splits the header on commas and appends the entries from rightmost to leftmost, so the resulting slice reads from the proxy closest to the server outward toward the original client. FromRequest is a convenience wrapper that pulls r.RemoteAddr and the X-Forwarded-For header from an *http.Request before delegating to Forwarded.
Several edge cases are handled to preserve parity with the Node original. An empty or missing header yields a single-element slice containing only the remote address. Each header entry is trimmed of surrounding whitespace, but empty entries (for example from a "a,,b" value) are preserved as empty strings rather than being dropped. Port stripping understands IPv4 "host:port", bracketed IPv6 "[::1]:8080" and "[::1]" forms, and bare IPv6 literals such as "::1" or "2001:db8::1", which are returned unchanged because they carry no port. Malformed input is never rejected; it is returned as-is.
The one intentional deviation from the JavaScript module is ergonomic rather than behavioral: because Go's *http.Request exposes RemoteAddr as a string instead of a socket object, the core logic lives in Forwarded, which accepts the remote address and header value directly, and FromRequest adapts a request to that signature. The addresses returned are plain strings and are not parsed into net.IP values, matching the string-based results of the npm package.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Forwarded ¶
Forwarded returns the list of addresses in the request chain.
The first element is remoteAddr (with any port stripped). The remaining elements are the comma-separated entries of the X-Forwarded-For value (xff), trimmed of surrounding whitespace and appended from the rightmost entry to the leftmost entry. Empty entries in the header are preserved as empty strings, matching the behavior of the npm "forwarded" module.
Example ¶
ExampleForwarded shows the core parsing behavior of the package. The first argument is the socket remote address, and the second is the raw X-Forwarded-For header value that the proxies appended as the request was relayed. The returned slice starts with the remote address (its port stripped) and then lists the header entries from rightmost to leftmost, so the proxy nearest the server appears first and the original client last. Surrounding whitespace on each header entry is trimmed away.
package main
import (
"fmt"
"github.com/malcolmston/express/forwarded"
)
func main() {
addrs := forwarded.Forwarded("127.0.0.1:1234", "10.0.0.1, 10.0.0.2, 192.168.0.1")
fmt.Println(addrs)
}
Output: [127.0.0.1 192.168.0.1 10.0.0.2 10.0.0.1]
Example (Ipv6) ¶
ExampleForwarded_ipv6 illustrates that IPv6 remote addresses are handled in both bracketed and bare forms. A bracketed literal with a port such as "[::1]:8080" has its port and brackets removed to yield "::1". A bare IPv6 literal carries no port and is returned unchanged. This matches the port stripping performed by the npm "forwarded" module. Only the remote address is normalized this way; header entries are only trimmed.
package main
import (
"fmt"
"github.com/malcolmston/express/forwarded"
)
func main() {
fmt.Println(forwarded.Forwarded("[::1]:8080", ""))
fmt.Println(forwarded.Forwarded("2001:db8::1", ""))
}
Output: [::1] [2001:db8::1]
Example (NoHeader) ¶
ExampleForwarded_noHeader demonstrates the behavior when no X-Forwarded-For header is present. With an empty header value the result is a single-element slice containing just the remote address. Any port on the remote address is removed first, so "127.0.0.1:8080" becomes "127.0.0.1". This is the common case for a request that reached the server directly without passing through a proxy. The result is always non-empty.
package main
import (
"fmt"
"github.com/malcolmston/express/forwarded"
)
func main() {
addrs := forwarded.Forwarded("127.0.0.1:8080", "")
fmt.Println(addrs)
}
Output: [127.0.0.1]
func FromRequest ¶
FromRequest returns the address chain for an *http.Request, reading r.RemoteAddr (with the port stripped) and the X-Forwarded-For header.
Example ¶
ExampleFromRequest shows the convenience wrapper for an *http.Request. It reads RemoteAddr and the X-Forwarded-For header from the request and delegates to Forwarded, so you do not have to extract those values yourself. Here the request arrived from socket 127.0.0.1 after passing through two proxies. The resulting chain lists the closest proxy first and the original client last. This is the form you would typically call from an HTTP handler.
package main
import (
"fmt"
"net/http"
"github.com/malcolmston/express/forwarded"
)
func main() {
r, _ := http.NewRequest("GET", "http://example.com", nil)
r.RemoteAddr = "127.0.0.1:1234"
r.Header.Set("X-Forwarded-For", "10.0.0.1, 10.0.0.2")
fmt.Println(forwarded.FromRequest(r))
}
Output: [127.0.0.1 10.0.0.2 10.0.0.1]
Types ¶
This section is empty.