go-web-utils
IP 工具包 (iputil)

GetClientIP

智能获取客户端真实 IP 地址

GetClientIP

智能获取客户端真实 IP 地址,自动处理各种代理和 CDN 场景。

函数签名

func GetClientIP(r *http.Request) string

参数

  • r *http.Request - HTTP 请求对象

返回值

  • string - 客户端 IP 地址字符串

获取逻辑

GetClientIP 函数按以下优先级获取客户端 IP:

优先级HTTP 头使用场景
1CF-Connecting-IPCloudflare CDN
2X-Real-IPNginx 反向代理
3X-Forwarded-For标准代理链
4RemoteAddr直连访问

基本用法

package main

import (
    "fmt"
    "net/http"
    "github.com/woodchen-ink/go-web-utils/iputil"
)

func handler(w http.ResponseWriter, r *http.Request) {
    clientIP := iputil.GetClientIP(r)
    fmt.Fprintf(w, "您的 IP 地址: %s", clientIP)
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

实际应用场景

1. 访问日志记录

func logMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        clientIP := iputil.GetClientIP(r)
        start := time.Now()
        
        next.ServeHTTP(w, r)
        
        duration := time.Since(start)
        log.Printf("[%s] %s %s - %v", 
            clientIP, r.Method, r.URL.Path, duration)
    })
}

2. API 限流

// 简单的内存限流器
var rateLimiter = make(map[string]int)
var mu sync.Mutex

func rateLimitMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        clientIP := iputil.GetClientIP(r)
        
        mu.Lock()
        count := rateLimiter[clientIP]
        if count >= 100 { // 每分钟最多 100 次请求
            mu.Unlock()
            http.Error(w, "请求过于频繁", http.StatusTooManyRequests)
            return
        }
        rateLimiter[clientIP] = count + 1
        mu.Unlock()
        
        next.ServeHTTP(w, r)
    })
}

3. IP 白名单控制

var allowedIPs = map[string]bool{
    "192.168.1.100": true,
    "10.0.0.50":     true,
    "203.0.113.10":  true,
}

func ipWhitelistMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        clientIP := iputil.GetClientIP(r)
        
        if !allowedIPs[clientIP] {
            http.Error(w, "访问被拒绝", http.StatusForbidden)
            log.Printf("拒绝访问来自 %s 的请求", clientIP)
            return
        }
        
        next.ServeHTTP(w, r)
    })
}

4. 内外网区分处理

func serviceHandler(w http.ResponseWriter, r *http.Request) {
    clientIP := iputil.GetClientIP(r)
    
    // 检查是否为私有网络 IP
    if iputil.IsPrivateIP(clientIP) {
        // 内网用户,提供管理员功能
        w.Header().Set("X-Access-Level", "admin")
        fmt.Fprintf(w, "内网管理员访问,IP: %s", clientIP)
    } else {
        // 外网用户,提供普通功能
        w.Header().Set("X-Access-Level", "user")
        fmt.Fprintf(w, "外网用户访问,IP: %s", clientIP)
    }
}

不同代理场景示例

Cloudflare 代理

客户端 IP: 203.0.113.45
请求头: CF-Connecting-IP: 203.0.113.45
结果: GetClientIP() 返回 "203.0.113.45"

Nginx 反向代理

客户端 IP: 203.0.113.45
请求头: X-Real-IP: 203.0.113.45
结果: GetClientIP() 返回 "203.0.113.45"

多级代理链

客户端 IP: 203.0.113.45
请求头: X-Forwarded-For: 203.0.113.45, 192.168.1.1, 10.0.0.1
结果: GetClientIP() 返回 "203.0.113.45" (第一个 IP)

直连访问

RemoteAddr: 203.0.113.45:54321
结果: GetClientIP() 返回 "203.0.113.45" (自动去除端口)

安全注意事项

1. 头部伪造风险

HTTP 头部可能被客户端伪造,在安全敏感场景中需要注意:

func secureGetClientIP(r *http.Request) string {
    ip := iputil.GetClientIP(r)
    
    // 验证 IP 格式
    if !iputil.IsValidIP(ip) {
        // 回退到直连 IP
        host, _, _ := net.SplitHostPort(r.RemoteAddr)
        return host
    }
    
    return ip
}

2. 信任代理环境

只在可信的代理环境中依赖代理头部:

func conditionalGetClientIP(r *http.Request, trustProxy bool) string {
    if !trustProxy {
        // 不信任代理,直接使用 RemoteAddr
        host, _, _ := net.SplitHostPort(r.RemoteAddr)
        return host
    }
    
    return iputil.GetClientIP(r)
}

性能说明

  • 零内存分配: 除了返回的字符串,函数不产生额外的内存分配
  • 快速查找: 头部查找基于 Go 标准库的高效实现
  • 短路求值: 一旦找到有效 IP 立即返回,避免不必要的处理

相关函数

  • IsValidIP - 验证获取的 IP 地址格式是否正确