GIN 反向代理功能

今天想试着做一个gin的反向代理,代理其他的服务,给前端使用,思路如下:

主要组成部分

  1. 跨域中间件(Cors 函数)

    • Cors 中间件函数通过在 HTTP 响应中设置合适的头部信息,允许跨域请求。
    • 它添加了允许的方法(例如 POSTGETOPTIONS 等),并向客户端公开特定的头部字段。
    • 对于预检请求(即 OPTIONS 请求),直接返回 "ok" 表示请求被允许。
    • 还包含一个 defer 函数,用于在发生 panic 时进行恢复处理。
  2. 反向代理中间件(ReverseProxy 函数)

    • ReverseProxy 函数将传入的请求转发到由 targetURL 指定的目标服务器。
    • 使用 httputil.NewSingleHostReverseProxy 为指定目标创建一个反向代理。
    • 调整请求的 host 和 URL,使其与目标服务器匹配,以确保请求被正确转发。
  3. 动态更新目标 URL(UpdateTargetURL 函数)

    • 该端点(/api/update-target)用于更新反向代理所使用的 targetURL,这是一个受读写锁保护的全局变量。
    • 接收包含 hostport 值的 JSON 负载,以设置新的目标 URL。
    • 更新后的 targetURL 会被格式化并存储,然后返回一个确认消息。
  4. 服务器启动函数(Start 函数)

    • Start 函数根据配置的模式(debug 或 release)初始化 Gin 服务器。
    • 全局应用 CORS 中间件,并设置一个用于更新目标 URL 的端点。
    • 设置反向代理路由,将 /v1/*proxyPath 下的所有请求转发到当前目标 URL。
    • 服务器监听并在端口 8080 上运行。

功能要求

  • 并发和安全性 :通过使用 mu 锁,确保对 targetURL 的线程安全访问,因为多个 goroutine 可能同时读取或更新此值。
  • 错误处理UpdateTargetURL 函数处理格式不正确的 JSON 请求,如果输入的 JSON 无效,则返回 400 Bad Request
  • 可配置的代理目标 :允许动态更新 targetURL 使该服务器设置更灵活,可以根据需要将请求路由到不同的后端服务器。
  • 调试 :在 Start 中检查配置的详细模式,以根据需要设置 Gin 的 debug 或 release 模式,用于在生产与开发环境中切换日志记录。

此设置非常适合需要将请求动态代理到不同后端服务器的场景,同时处理跨域请求并确保并发访问的安全性。

代码实现
go 复制代码
package web

import (
	"fmt"
	"net/http"
	"net/http/httputil"
	"net/url"
	"sync"

	"github.com/gin-gonic/gin"
	"client/backend/internal/config"
)

var (
	targetURL string       = "http://127.0.0.1:8080" // Default target URL
	mu        sync.RWMutex                              // Mutex for safe concurrent access
)

// 跨域中间件
func Cors() gin.HandlerFunc {
	return func(c *gin.Context) {
		method := c.Request.Method
		origin := c.Request.Header.Get("Origin") //请求头部
		if origin != "" {
			//接收客户端发送的origin (重要!)
			c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
			//服务器支持的所有跨域请求的方法
			c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE")
			//允许跨域设置可以返回其他子段,可以自定义字段
			c.Header("Access-Control-Allow-Headers", "Content-Type,Authorization, Content-Length, X-CSRF-Token, Token,session")
			// 允许浏览器(客户端)可以解析的头部 (重要)
			c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers")
			//设置缓存时间
			c.Header("Access-Control-Max-Age", "172800")
			//允许客户端传递校验信息比如 cookie (重要)
			c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization, X-Requested-With")
			c.Header("Access-Control-Allow-Credentials", "true")

		}

		//允许类型校验
		if method == "OPTIONS" {
			c.JSON(http.StatusOK, "ok!")
		}

		defer func() {
			if err := recover(); err != nil {
				fmt.Println("Panic info is: %v", err)
			}
		}()

		c.Next()
	}
}

// ReverseProxy function to forward requests to the target server.
// ReverseProxy function to forward requests to the current target server.
func ReverseProxy() gin.HandlerFunc {
	return func(c *gin.Context) {
		mu.RLock() // Lock for reading
		defer mu.RUnlock()

		target, err := url.Parse(targetURL)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid target URL"})
			return
		}

		proxy := httputil.NewSingleHostReverseProxy(target)

		// Update the request to the target URL
		c.Request.Host = target.Host
		c.Request.URL.Host = target.Host
		c.Request.URL.Scheme = target.Scheme

		// Serve the request using the reverse proxy
		proxy.ServeHTTP(c.Writer, c.Request)
	}
}

type BackendServer struct {
	Host string `json:"host"`
	Port int    `json:"port"`
}

// UpdateTargetURL updates the target URL dynamically.
func UpdateTargetURL(c *gin.Context) {

	var options BackendServer

	// Bind the JSON body to the newTarget struct
	if err := c.ShouldBindJSON(&options); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body", "code": 400})
		return
	}

	mu.Lock() // Lock for writing
	defer mu.Unlock()

	// Update the global targetURL variable
	targetURL = fmt.Sprintf("%s:%d", options.Host, options.Port)
	c.JSON(http.StatusOK, gin.H{"message": "Target URL updated", "new_target": targetURL, "code": 200})
}
func Start() {
	if config.C().Verbose {
		gin.SetMode(gin.DebugMode)
	} else {
		gin.SetMode(gin.ReleaseMode)
	}

	router := gin.Default()

	router.Use(Cors())
	// Route to update the target server URL
	router.POST("/api/update", UpdateTargetURL)
	// Proxy all requests to the /api endpoint to the current target server
	router.Any("/v1/*proxyPath", ReverseProxy())

	// Run the Gin server on port 8080
	if err := router.Run(":8080"); err != nil {
		panic(err)
	}
}
相关推荐
Asthenia04121 小时前
Spring扩展点与工具类获取容器Bean-基于ApplicationContextAware实现非IOC容器中调用IOC的Bean
后端
bobz9651 小时前
ovs patch port 对比 veth pair
后端
Asthenia04121 小时前
Java受检异常与非受检异常分析
后端
uhakadotcom1 小时前
快速开始使用 n8n
后端·面试·github
JavaGuide2 小时前
公司来的新人用字符串存储日期,被组长怒怼了...
后端·mysql
bobz9652 小时前
qemu 网络使用基础
后端
Asthenia04122 小时前
面试攻略:如何应对 Spring 启动流程的层层追问
后端
Asthenia04122 小时前
Spring 启动流程:比喻表达
后端
Asthenia04123 小时前
Spring 启动流程分析-含时序图
后端
ONE_Gua3 小时前
chromium魔改——CDP(Chrome DevTools Protocol)检测01
前端·后端·爬虫