Go+Gin实现获取本机IP与访问IP
之前开发系统时,既要绑定本机指定IP启动服务,又要记录每个请求的访问来源IP,踩了不少坑——比如本机多网卡时拿到无效的回环地址,部署到服务器后因反向代理拿到的是代理IP而非真实访问IP。
今天把Go+Gin环境下的解决方案整理出来,覆盖各种场景和问题处理。
先明确两个核心场景的区别:本机IP是服务所在机器的网络地址,用于服务绑定、跨服务通信等;访问IP是发起请求的客户端地址,用于日志记录、权限校验等。
下面分别讲实现方法。
一、获取本机IP:处理多网卡+无效地址
直接调用系统方法可能拿到127.0.0.1(回环地址)或IPv6地址,实际开发中更需要“能被其他机器访问的IPv4地址”。
核心思路是:遍历所有网卡的网络地址,过滤掉回环地址、IPv6地址和无效地址,筛选出可用的本机IPv4。
完整代码如下:
package main
import (
"fmt"
"net"
"strings"
)
// GetLocalIP 获取本机可用IPv4地址(非回环)
func GetLocalIP() (string, error) {
// 获取所有网卡接口信息
interfaces, err := net.Interfaces()
if err != nil {
return "", fmt.Errorf("获取网卡信息失败:%v", err)
}
// 遍历每个网卡
for _, iface := range interfaces {
// 跳过禁用的网卡
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
continue // 网卡未启用或为回环网卡,跳过
}
// 获取网卡对应的所有地址
addrs, err := iface.Addrs()
if err != nil {
return "", fmt.Errorf("获取网卡地址失败:%v", err)
}
// 遍历每个地址,筛选IPv4
for _, addr := range addrs {
// 转换为IP网络地址格式
ipNet, ok := addr.(*net.IPNet)
if !ok || ipNet.IP.IsLoopback() {
continue // 不是IP网络地址或为回环地址,跳过
}
// 筛选IPv4地址(排除IPv6)
if ip4 := ipNet.IP.To4(); ip4 != nil {
return ip4.String(), nil
}
}
}
return "", fmt.Errorf("未找到可用的本机IPv4地址")
}
// GetAllLocalIP 获取所有本机IPv4地址(含多网卡场景)
func GetAllLocalIP() ([]string, error) {
var ips []string
interfaces, err := net.Interfaces()
if err != nil {
return nil, err
}
for _, iface := range interfaces {
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
continue
}
addrs, err := iface.Addrs()
if err != nil {
return nil, err
}
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if !ok || ipNet.IP.IsLoopback() {
continue
}
if ip4 := ipNet.IP.To4(); ip4 != nil {
ips = append(ips, ip4.String())
}
}
}
if len(ips) == 0 {
return nil, fmt.Errorf("未找到本机IPv4地址")
}
return ips, nil
}
func main() {
// 测试获取单个可用IP
localIP, err := GetLocalIP()
if err != nil {
fmt.Printf("获取单个本机IP失败:%v\n", err)
} else {
fmt.Printf("本机可用IP:%s\n", localIP)
}
// 测试获取所有IP(多网卡场景)
allIPs, err := GetAllLocalIP()
if err != nil {
fmt.Printf("获取所有本机IP失败:%v\n", err)
} else {
fmt.Printf("本机所有IPv4地址:%v\n", allIPs)
}
}- 单IP场景:用
GetLocalIP(),适合服务绑定指定IP启动(如Gin绑定非0.0.0.0的地址) - 多网卡场景:用
GetAllLocalIP(),比如服务器有内网和外网双网卡,需要展示所有可用IP时使用 - 过滤逻辑:代码中通过
net.FlagUp判断网卡是否启用,通过To4()筛选IPv4,避免拿到无效地址
二、Gin获取访问IP:适配直连与反向代理
Gin获取访问IP的核心是解析请求的RemoteAddr字段,但线上环境通常有Nginx等反向代理,直接获取会拿到代理服务器IP,需通过代理头信息解析真实访问IP。
下面分两种场景实现。
(一)直连场景(无代理):简单获取
无反向代理时,请求直接打到Gin服务,通过c.ClientIP()或解析RemoteAddr即可获取,前者是Gin封装的便捷方法。
package main
import (
"fmt"
"net"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 接口1:用Gin封装方法获取
r.GET("/get-client-ip1", func(c *gin.Context) {
clientIP := c.ClientIP() // Gin封装,直接获取
c.JSON(200, gin.H{
"client_ip": clientIP,
"method": "Gin ClientIP()",
})
})
// 接口2:手动解析RemoteAddr(备用方案)
r.GET("/get-client-ip2", func(c *gin.Context) {
// RemoteAddr格式:"IP:端口",需拆分出IP
remoteAddr := c.Request.RemoteAddr
ip, _, err := net.SplitHostPort(remoteAddr)
if err != nil {
c.JSON(500, gin.H{"error": fmt.Sprintf("解析IP失败:%v", err)})
return
}
c.JSON(200, gin.H{
"client_ip": ip,
"method": "解析RemoteAddr",
})
})
_ = r.Run(":8080")
}测试:本地用Postman访问http://localhost:8080/get-client-ip1,返回"client_ip":"127.0.0.1",符合预期。
(二)反向代理场景(Nginx等):解析真实IP
线上部署时,Gin通常放在Nginx等反向代理后,此时RemoteAddr是代理服务器IP,真实访问IP会放在X-Forwarded-For或X-Real-IP等请求头中。需先配置代理传递头信息,再在Gin中解析。
步骤1:配置反向代理(以Nginx为例)
在Nginx配置中添加请求头,将真实IP传递给Gin服务:
server {
listen 80;
server_name your-domain.com;
location / {
# 转发请求到Gin服务
proxy_pass http://127.0.0.1:8080;
# 传递真实访问IP到请求头
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; # 真实访问IP
# 多个代理时,记录所有代理IP(第一个为真实IP)
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}步骤2:Gin解析真实IP
Gin的ClientIP()默认会解析X-Forwarded-For和X-Real-IP,但需配置“信任的代理网段”,避免被伪造IP攻击。
package main
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"net"
)
func main() {
// 方式1:全局配置信任的代理网段(推荐)
// 假设Nginx代理的IP是127.0.0.1,添加到信任列表
gin.ForwardedByClientIP = true
// 配置信任的代理IP段(支持单个IP或CIDR网段)
trustedProxies := []string{"127.0.0.1", "192.168.1.0/24"}
err := gin.SetTrustedProxies(trustedProxies)
if err != nil {
panic(fmt.Sprintf("配置信任代理失败:%v", err))
}
r := gin.Default()
// 此时c.ClientIP()会自动解析真实IP
r.GET("/get-real-ip", func(c *gin.Context) {
realIP := c.ClientIP()
// 也可手动解析X-Forwarded-For(适合自定义场景)
xff := c.Request.Header.Get("X-Forwarded-For")
c.JSON(200, gin.H{
"real_ip": realIP,
"x_forwarded_for": xff,
"note": "X-Forwarded-For第一个IP为真实IP",
})
})
// 方式2:局部中间件配置(适合单接口需求)
r.GET("/get-real-ip-local", func(c *gin.Context) {
// 手动获取X-Real-IP
xRealIP := c.Request.Header.Get("X-Real-IP")
if xRealIP != "" {
c.Set("client_ip", xRealIP)
c.Next()
return
}
// 若X-Real-IP为空,解析X-Forwarded-For
xff := c.Request.Header.Get("X-Forwarded-For")
if xff != "" {
// X-Forwarded-For格式:"真实IP,代理IP1,代理IP2"
realIP := strings.Split(xff, ",")[0]
c.Set("client_ip", realIP)
c.Next()
return
}
// 都为空时用默认方法
c.Set("client_ip", c.ClientIP())
c.Next()
}, func(c *gin.Context) {
clientIP, _ := c.Get("client_ip")
c.JSON(200, gin.H{"real_ip": clientIP})
})
_ = r.Run(":8080")
}测试:通过Nginx访问域名,返回的real_ip会是客户端的真实公网IP,而非127.0.0.1。
常见问题
Q1. 无论是否反向代理,Gin都拿不到请求IP?
这种情况可以使用自定义 header,前端在发起请求时设置一个预定的header,比如x-request-ip,将实际请求 ip 设置在该 header,后端 gin 通过该 header 进行获取
总结
Go+Gin获取IP的核心是“场景适配”:
获取本机IP要处理多网卡和无效地址,推荐用遍历筛选法;
获取访问IP要区分直连和反向代理场景,反向代理需配置代理头和信任列表。
关键避坑点:本机IP务必过滤回环地址,反向代理必须配置信任代理网段,多场景下可通过网卡名称或IP网段精准筛选。
如果大家遇到云服务器(阿里云、腾讯云)等特殊环境的IP获取问题,欢迎在评论区交流~
版权声明
未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!