当前位置: 首页 > news >正文

【GO语言小案例手记】基于GIN的简易代理网关

基于GIN的简易代理网关

  • 背景
  • 目标
  • 开工
    • 依赖
    • 主体代码
    • 配置文件
  • 后记

背景

正好最近对GO也有点兴趣,搞个小项目练练手。

目标

  1. 网关需要能够根据路由自动映射到服务
  2. 支持轮询、加权轮询、随机轮询三种算法
  3. 简单好理解好使用,最好一个配置文件就能跑起来
  4. 网关本身需要能够健康检查
  5. 能够劫持请求修改请求头

开工

依赖

module test-gingo 1.22.0require (github.com/bytedance/sonic v1.9.1 // indirectgithub.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirectgithub.com/gabriel-vasile/mimetype v1.4.2 // indirectgithub.com/gin-contrib/sse v0.1.0 // indirectgithub.com/gin-gonic/gin v1.9.1 // indirectgithub.com/go-playground/locales v0.14.1 // indirectgithub.com/go-playground/universal-translator v0.18.1 // indirectgithub.com/go-playground/validator/v10 v10.14.0 // indirectgithub.com/goccy/go-json v0.10.2 // indirectgithub.com/json-iterator/go v1.1.12 // indirectgithub.com/klauspost/cpuid/v2 v2.2.4 // indirectgithub.com/leodido/go-urn v1.2.4 // indirectgithub.com/mattn/go-isatty v0.0.19 // indirectgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirectgithub.com/modern-go/reflect2 v1.0.2 // indirectgithub.com/pelletier/go-toml/v2 v2.0.8 // indirectgithub.com/twitchyliquid64/golang-asm v0.15.1 // indirectgithub.com/ugorji/go/codec v1.2.11 // indirectgolang.org/x/arch v0.3.0 // indirectgolang.org/x/crypto v0.9.0 // indirectgolang.org/x/net v0.10.0 // indirectgolang.org/x/sys v0.8.0 // indirectgolang.org/x/text v0.9.0 // indirectgoogle.golang.org/protobuf v1.30.0 // indirectgopkg.in/yaml.v3 v3.0.1 // indirect
)

主体代码

package mainimport ("encoding/json""log""math/rand""net/http""net/http/httputil""net/url""os""sync/atomic""time""github.com/gin-gonic/gin"
)type Target struct {URL    string `json:"url"`Weight int    `json:"weight"`
}type RouteConfig struct {Path     string   `json:"path"`Targets  []Target `json:"targets"`Strategy string   `json:"strategy"`
}type Config struct {Routes []RouteConfig `json:"routes"`
}// 更新Balancer结构体
type Balancer struct {targets     []*url.URLweights     []inttotalWeight intstrategy    stringcounter     uint64rnd         *rand.RandweightIndex intweightAccum int
}// 更新NewBalancer函数
func NewBalancer(route RouteConfig, rnd *rand.Rand) (*Balancer, error) {var urls []*url.URLvar weights []inttotalWeight := 0for _, t := range route.Targets {u, err := url.Parse(t.URL)if err != nil {return nil, err}urls = append(urls, u)weights = append(weights, t.Weight)totalWeight += t.Weight}return &Balancer{targets:     urls,weights:     weights,totalWeight: totalWeight,strategy:    route.Strategy,rnd:         rnd,}, nil
}// 修改Next方法中的随机选择逻辑
func (b *Balancer) Next() *url.URL {switch b.strategy {case "random":return b.targets[b.rnd.Intn(len(b.targets))]case "weighted-round-robin":for {b.weightIndex = (b.weightIndex + 1) % len(b.targets)if b.weightIndex == 0 {b.weightAccum -= b.totalWeightif b.weightAccum < 0 {b.weightAccum = 0}}if b.weights[b.weightIndex] > b.weightAccum {b.weightAccum += 1return b.targets[b.weightIndex]}}default: // 普通round-robinidx := atomic.AddUint64(&b.counter, 1) % uint64(len(b.targets))return b.targets[idx]}
}func main() {rnd := rand.New(rand.NewSource(time.Now().UnixNano()))// 读取配置文件configFile, err := os.ReadFile("config.json")if err != nil {panic("无法读取配置文件: " + err.Error())}var config Configif err := json.Unmarshal(configFile, &config); err != nil {panic("解析配置文件失败: " + err.Error())}r := gin.Default()// 添加请求日志中间件r.Use(func(c *gin.Context) {start := time.Now()c.Next()log.Printf("[%s] %s %s %d %s",c.Request.Method,c.Request.URL.Path,c.ClientIP(),c.Writer.Status(),time.Since(start),)})// 为每个路由配置创建反向代理for _, route := range config.Routes {balancer, err := NewBalancer(route, rnd) // 传入随机数生成器if err != nil {panic("创建负载均衡器失败: " + err.Error())}r.Any(route.Path+"/*any", func(c *gin.Context) {target := balancer.Next()proxy := httputil.NewSingleHostReverseProxy(target)// 修改请求头originalDirector := proxy.Directorproxy.Director = func(req *http.Request) {originalDirector(req)req.Header.Set("X-Forwarded-For", c.ClientIP())req.Header.Set("X-Proxy", "gin-reverse-proxy")req.Header.Set("X-Request-Id", time.Now().Format("20060102150405"))}// 转发请求proxy.ServeHTTP(c.Writer, c.Request)})}// 健康检查端点r.GET("/health", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"status": "ok"})})// 启动服务r.Run(":8080")
}

配置文件

{"routes": [{"path": "/api/users","targets": [{"url": "http://user-service-1:10031","weight": 3},{"url": "http://user-service-2:10031","weight": 2},{"url": "http://user-service-3:10031","weight": 1}],"strategy": "weighted-round-robin"},{"path": "/api/products","targets": [{"url": "http://product-service-1:21010","weight": 3},{"url": "http://product-service-2:21010","weight": 2}],"strategy": "random"}]
}

后记

GO语言起手是真的快,唯一不安全的地方在于GO完全由阿美莉卡控制,如果未来脱钩越来越狠,那GO语言作为生产要素的可持续获得性有点让人怀疑

相关文章:

  • SQL实战:02之连续数问题求解
  • 基于单片机的游泳馆智能管理系统
  • 借助deepseek和vba编程实现一张表格数据转移到多张工作簿的表格中
  • LVGL在VScode的WSL2中仿真
  • Linux之安装配置Nginx
  • Redis--预备知识以及String类型
  • 【AI 加持下的 Python 编程实战 2_10】DIY 拓展:从扫雷小游戏开发再探问题分解与 AI 代码调试能力(中)
  • 双目视觉中,利用左右图像互补信息来补全彼此缺失区域
  • ETL 数据集成都包含哪些?
  • 【金仓数据库征文】——金仓数据库:国产数据库的卓越之选
  • 开发并发布一个属于自己的包(npm)
  • 卸载rpm包
  • 2. Linux开发工具
  • 亚远景-如何通过ASPICE评估满足功能安全(ISO 26262)合规需求?
  • OFDM 信道表示(3)
  • 【网络安全】网络钓鱼的类型
  • React:什么是Hook?通俗易懂的讲讲
  • MuJoCo 机械臂 PPO 强化学习逆向运动学(IK)
  • 分页查询优惠券
  • 逐步了解蓝牙 LE 配对(物联网网络安全)
  • 一周文化讲座|“不一样的社会观察”
  • 吏亦有道|秦汉的发明家与技术传承
  • 《2025职场人阅读报告》:超半数会因AI改变阅读方向
  • 百台新车首秀上海车展,跨国车企联手中国技术开启智能化下半场
  • 海关总署牵头部署开展跨境贸易便利化专项行动
  • 长三角与粤港澳大湾区融合发展,无锡何以成为窗口?