64 lines
1.3 KiB
Go
64 lines
1.3 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
func extractClientIP(r *http.Request) *string {
|
|
if r == nil {
|
|
return nil
|
|
}
|
|
|
|
// Priority headers commonly set by reverse proxies, load balancers, and CDNs.
|
|
headers := []string{
|
|
"CF-Connecting-IP", // Cloudflare
|
|
"True-Client-IP", // Cloudflare Enterprise, Akamai
|
|
"X-Real-IP", // Nginx, Traefik
|
|
}
|
|
|
|
for _, header := range headers {
|
|
if val := strings.TrimSpace(r.Header.Get(header)); val != "" {
|
|
if ip := parseIP(val); ip != "" {
|
|
return &ip
|
|
}
|
|
}
|
|
}
|
|
|
|
// X-Forwarded-For can contain multiple comma-separated IPs: client, proxy1, proxy2. The original client IP is the first entry.
|
|
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
|
for _, part := range strings.Split(xff, ",") {
|
|
if ip := parseIP(part); ip != "" {
|
|
return &ip
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback to RemoteAddr (typically "host:port" or "[ipv6]:port").
|
|
if r.RemoteAddr != "" {
|
|
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
|
if err != nil {
|
|
host = r.RemoteAddr
|
|
}
|
|
if ip := parseIP(host); ip != "" {
|
|
return &ip
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseIP(raw string) string {
|
|
raw = strings.TrimSpace(raw)
|
|
raw = strings.TrimPrefix(raw, "[")
|
|
raw = strings.TrimSuffix(raw, "]")
|
|
|
|
ip := net.ParseIP(raw)
|
|
if ip == nil {
|
|
return ""
|
|
}
|
|
|
|
return ip.String()
|
|
}
|