Golang Go语言中怎么把全局共享修改为并发的

发布于 1周前 作者 eggper 来自 Go语言

有没有 GO 的大佬,能否帮忙修改这个反向代理的代码,是从 redis 里拿出 SSL 证书和反代的目标地址,
目前主要是全局变量的问题,要修改为并发安全(刚入门 GO 还没研究明白),
问 AI 也没能解决他只是建议用上下文(或许是免费的 AI 不行)。
非常感谢!

package main

import ( “context” “crypto/tls” “errors” “fmt” “github.com/redis/go-redis/v9” “net” “net/http” “net/http/httputil” “net/url” “strings” “time” )

var ( httpAddr = “:80” httpsAddr = “:443” redisClient *redis.Client )

type proxyInfo struct { targetUrl string requestPath string requestRawQuery string requestHeader map[string]string }

func init() { redisClient = redis.NewClient(&redis.Options{ Addr: “127.0.0.1:6379”, Password: “”, DB: 0, }) }

func main() { //创建 httpTCP tcpConn, err := net.Listen(“tcp”, httpAddr) if err != nil { panic(err) } defer tcpConn.Close()

//创建 httpsTCP
tcpsConn, err := net.Listen("tcp", httpsAddr)
if err != nil {
	panic(err)
}
defer tcpsConn.Close()

pi := &proxyInfo{}

tlsConn := tls.NewListener(tcpsConn, &tls.Config{
	GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
		return pi.getCertificate(clientHello)
	},
})

httpServer := &http.Server{
	Handler: pi.proxyRequestHandler(),
}

go func() {
	httpServer.Serve(tcpConn)
}()

go func() {
	httpServer.Serve(tlsConn)
}()

select {}

}

// 反向代理 func (pi *proxyInfo) newProxy() (*httputil.ReverseProxy, error) {

targetUrl, err := url.Parse(pi.targetUrl)
if err != nil {
	return nil, err
}

targetUrl.Path = pi.requestPath
targetUrl.RawQuery = pi.requestRawQuery

fmt.Println("反代的地址:", targetUrl.String())
proxy := httputil.NewSingleHostReverseProxy(targetUrl)

//连接配置
proxy.Transport = &http.Transport{
	Proxy: http.ProxyFromEnvironment,
	DialContext: (&net.Dialer{
		Timeout:   60 * time.Second,
		KeepAlive: 60 * time.Second,
	}).DialContext,
	ForceAttemptHTTP2:     true,
	MaxIdleConns:          100,
	IdleConnTimeout:       90 * time.Second,
	TLSHandshakeTimeout:   10 * time.Second,
	ExpectContinueTimeout: 1 * time.Second,
	MaxIdleConnsPerHost:   20,
}

originalDirector := proxy.Director
proxy.Director = func(req *http.Request) {
	originalDirector(req)
	req.URL = targetUrl
	req.Host = targetUrl.Host
	for k, v := range pi.requestHeader {
		//fmt.Println("添加请求头:", k, v)
		req.Header.Set(k, v)
	}
}

proxy.ModifyResponse = pi.modifyResponse()
proxy.ErrorHandler = pi.errorHandler()
return proxy, nil

}

// 根据客户端 ClientHello 查询 redis 里域名信息 func (pi *proxyInfo) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { hostName := clientHello.ServerName

//判断不符合域名长度的 SSL 请求
if len(hostName) < 4 {
	return nil, errors.New(hostName + ",域名长度不符合")
}

//查询 redis 里的域名 SSL 证书
hostConf, err := pi.getHostConf(hostName)
if err != nil {
	return nil, err
}

certPublic := []byte(hostConf["certPublic"])
certPrivate := []byte(hostConf["certPrivate"])

certAndKey, err := tls.X509KeyPair(certPublic, certPrivate)
if err != nil {
	return nil, err
}

return &certAndKey, nil

}

// 处理代理请求 func (pi *proxyInfo) proxyRequestHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) {

	//不是 https 的请求
	if r.TLS == nil {
		_, err := pi.getHostConf(getHostName(r.Host))
		if err != nil {
			w.WriteHeader( http.StatusBadRequest)
			w.Write([]byte(err.Error()))
			return
		}
	}

	pi.requestPath = r.URL.Path
	pi.requestRawQuery = r.URL.RawQuery

	requestHeader := make(map[string]string)
	requestHeader["Referer"] = pi.targetUrl
	requestHeader["User-Agent"] = r.Header.Get("User-Agent")
	requestHeader["Accept"] = r.Header.Get("Accept")
	pi.requestHeader = requestHeader

	//反代
	proxy, err := pi.newProxy()
	if err != nil {
		panic(err)
	}

	proxy.ServeHTTP(w, r)
}

}

// 修改 http 响应数据 func (pi *proxyInfo) modifyResponse() func(*http.Response) error { return func(r *http.Response) error { typeStr := r.Header.Get(“Content-Type”) fmt.Println(typeStr) return nil } }

// 错误处理器 func (pi *proxyInfo) errorHandler() func( http.ResponseWriter, *http.Request, error) { return func(w http.ResponseWriter, req *http.Request, err error) { fmt.Printf(“Got error while modifying response: %v \n”, err) w.WriteHeader( http.StatusInternalServerError) w.Write([]byte(“server error”)) return } }

// 获取域名的配置信息 func (pi *proxyInfo) getHostConf(hostName string) (map[string]string, error) { hostConf, err := redisClient.HGetAll(context.Background(), hostName).Result() if err != nil { return nil, err }

//模拟返回 SSL 证书
//hostConf["certPublic"] = "-----BEGIN CERTIFICATE-----\n"
//hostConf["certPrivate"] = "-----BEGIN CERTIFICATE-----\n"
//反代的目标网址
//hostConf["targetUrl"] = "https://www.baidu.com"

//反代的目标网址
pi.targetUrl = hostConf["targetUrl"]

return hostConf, nil

}

// 获取不含端口的 host func getHostName(rawUrl string) string { if !strings.HasPrefix(“http://”, rawUrl) || !strings.HasPrefix(“https://”, rawUrl) { rawUrl = “http://” + rawUrl } u, err := url.Parse(rawUrl) if err != nil { return “” } return u.Hostname() }


Golang Go语言中怎么把全局共享修改为并发的

更多关于Golang Go语言中怎么把全局共享修改为并发的的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

package main

import (
“context”
“crypto/tls”
“errors”
“fmt”
github.com/redis/go-redis/v9
“net”
“net/http”
“net/http/httputil”
“net/url”
“strings”
“sync”
“time”
)

var (
httpAddr = “:80”
httpsAddr = “:443”
redisClient *redis.Client
)

type proxyInfo struct {
mu sync.RWMutex
targetUrl string
requestPath string
requestRawQuery string
requestHeader map[string]string
}

func init() {
redisClient = redis.NewClient(&redis.Options{
Addr: “127.0.0.1:6379”,
Password: “”,
DB: 0,
})
}

func main() {
// 创建 httpTCP
tcpConn, err := net.Listen(“tcp”, httpAddr)
if err != nil {
panic(err)
}
defer tcpConn.Close()

// 创建 httpsTCP
tcpsConn, err := net.Listen(“tcp”, httpsAddr)
if err != nil {
panic(err)
}
defer tcpsConn.Close()

pi := &proxyInfo{
requestHeader: make(map[string]string),
}

tlsConn := tls.NewListener(tcpsConn, &tls.Config{
GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
return pi.getCertificate(clientHello)
},
})

httpServer := &http.Server{
Handler: pi.proxyRequestHandler(),
}

go func() {
if err := httpServer.Serve(tcpConn); err != nil {
fmt.Println(“HTTP server error:”, err)
}
}()

go func() {
if err := httpServer.Serve(tlsConn); err != nil {
fmt.Println(“HTTPS server error:”, err)
}
}()

select {}
}

// 反向代理
func (pi *proxyInfo) newProxy() (*httputil.ReverseProxy, error) {
pi.mu.RLock()
defer pi.mu.RUnlock()

targetUrl, err := url.Parse(pi.targetUrl)
if err != nil {
return nil, err
}

targetUrl.Path = pi.requestPath
targetUrl.RawQuery = pi.requestRawQuery

fmt.Println(“反代的地址:”, targetUrl.String())
proxy := httputil.NewSingleHostReverseProxy(targetUrl)

// 连接配置
proxy.Transport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 60 * time.Second,
KeepAlive: 60 * time.Second,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
MaxIdleConnsPerHost: 20,
}

originalDirector := proxy.Director
proxy.Director = func(req *http.Request) {
originalDirector(req)
req.URL = targetUrl
req.Host = targetUrl.Host
for k, v := range pi.requestHeader {
req.Header.Set(k, v)
}
}

proxy.ModifyResponse = pi.modifyResponse()
proxy.ErrorHandler = pi.errorHandler()
return proxy, nil
}

// 根据客户端 ClientHello 查询 redis 里域名信息
func (pi *proxyInfo) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
hostName := clientHello.ServerName

// 判断不符合域名长度的 SSL 请求
if len(hostName) < 4 {
return nil, errors.New(hostName + “,域名长度不符合”)
}

// 查询 redis 里的域名 SSL 证书
hostConf, err := pi.getHostConf(hostName)
if err != nil {
return nil, err
}

certPublic := []byte(hostConf[“certPublic”])
certPrivate := []byte(hostConf[“certPrivate”])

certAndKey, err := tls.X509KeyPair(certPublic, certPrivate)
if err != nil {
return nil, err
}

return &certAndKey, nil
}

// 处理代理请求
func (pi *proxyInfo) proxyRequestHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 不是 https 的请求
if r.TLS == nil {
_, err := pi.getHostConf(getHostName(r.Host))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
}

pi.mu.Lock()
defer pi.mu.Unlock()

pi.requestPath = r.URL.Path
pi.requestRawQuery = r.URL.RawQuery

// 清空并设置请求头
pi.requestHeader = make(map[string]string)
pi.requestHeader[“Referer”] = pi.targetUrl
pi.requestHeader[“User-Agent”] = r.Header.Get(“User-Agent”)
pi.requestHeader[“Accept”] = r.Header.Get(“Accept”)

// 反代
proxy, err := pi.newProxy()
if err != nil {
http.Error(w, "Failed to create proxy: "+err.Error(), http.StatusInternalServerError)
return
}

proxy.ServeHTTP(w, r)
}
}

// 修改 http 响应数据
func (pi *proxyInfo) modifyResponse() func(*http.Response) error {
return func(r *http.Response) error {
typeStr := r.Header.Get(“Content-Type”)
fmt.Println(“响应内容类型:”, typeStr)
return nil
}
}

// 错误处理器
func (pi *proxyInfo) errorHandler() func( http.ResponseWriter, *http.Request, error) {
return func(w http.ResponseWriter, req *http.Request, err error) {
fmt.Printf(“Got error while modifying response: %v \n”, err)
http.Error(w, “server error”, http.StatusInternalServerError)
return
}
}

// 获取域名的配置信息
func (pi *proxyInfo) getHostConf(hostName string) (map[string]string, error) {
hostConf, err := redisClient.HGetAll(context.Background(), hostName).Result()
if err != nil {
return nil, err
}

// 检查是否存在目标网址
if targetUrl, ok := hostConf[“targetUrl”]; ok {
pi.mu.Lock()
pi.targetUrl = targetUrl
pi.mu.Unlock()
} else {
return nil, errors.New(“missing targetUrl in configuration”)
}

return hostConf, nil
}

// 获取不含端口的 host
func getHostName(rawUrl string) string {
if !strings.HasPrefix(rawUrl, “http://”) && !strings.HasPrefix(rawUrl, “https://”) {
rawUrl = “http://” + rawUrl
}
u, err := url.Parse(rawUrl)
if err != nil {
return “”
}
return u.Hostname()
}

更多关于Golang Go语言中怎么把全局共享修改为并发的的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你这个是加锁的,不清楚是否可以应对一个请求反代花了很长的时间才结束,那么锁没有释放前是否就无法响应新的请求

每次请求创建一次 proxyInfo 副本就好了,可以运行加上-race 参数测试并发问题。

或者把这些参数通过 newProxy 函数传递过去也行。

go<br>func proxyRequestHandler() http.HandlerFunc {<br> return func(w http.ResponseWriter, r *http.Request) {<br> pi := &amp;proxyInfo{}<br> //不是 https 的请求<br> if r.TLS == nil {<br> _, err := pi.getHostConf(getHostName(r.Host))<br> if err != nil {<br> w.WriteHeader( http.StatusBadRequest)<br> w.Write([]byte(err.Error()))<br> return<br> }<br> }<br><br> pi.requestPath = r.URL.Path<br> pi.requestRawQuery = r.URL.RawQuery<br><br> requestHeader := make(map[string]string)<br> requestHeader["Referer"] = pi.targetUrl<br> requestHeader["User-Agent"] = r.Header.Get("User-Agent")<br> requestHeader["Accept"] = r.Header.Get("Accept")<br> pi.requestHeader = requestHeader<br><br> //反代<br> proxy, err := pi.newProxy()<br> if err != nil {<br> panic(err)<br> }<br><br> proxy.ServeHTTP(w, r)<br> }<br>}<br>

你的方法我现在就在用的,但唯一的缺点是要查询两次 redis ,一次在 tls 的 GetCertificate 里面查询拿到 SSL 证书建立握手,然后再到后续 http 的 Handler 里面再查询一次。
有没有办法在 GetCertificate 查询后拿到的数据给到 Handler 里面使用。
如果是 http 就没有这个问题直接在 Handler 查询。现在要兼容 http 和 https 两种,希望是在 tls 的 GetCertificate 查了就不要在后面的 Handler 再查一次

在Go语言中,实现全局共享变量的并发修改,通常会使用到goroutines和通道(channels),以及sync包中的互斥锁(Mutex)或原子操作(atomic)。以下是一些常见的方法:

  1. 使用互斥锁(Mutex)sync.Mutex可以用来保护共享资源,确保在任何时候只有一个goroutine可以访问这个资源。在修改共享变量之前,锁定互斥锁,修改完成后解锁。

    var mu sync.Mutex
    var sharedVar int
    
    func modifySharedVar(newValue int) {
        mu.Lock()
        sharedVar = newValue
        mu.Unlock()
    }
    
  2. 使用通道(Channels): 通道是Go语言中的一种用于goroutines之间通信的机制。你可以通过通道来发送和接收消息,从而实现对共享变量的安全访问和修改。

    var sharedVar int
    ch := make(chan int)
    
    go func() {
        for newValue := range ch {
            sharedVar = newValue
        }
    }()
    
    // 在其他goroutine中发送新值
    ch <- 10
    
  3. 使用原子操作(atomic)sync/atomic包提供了一些底层的原子操作,可以直接对基本数据类型进行并发安全的修改。

    import "sync/atomic"
    
    var sharedVar int32
    
    func modifySharedVar(newValue int32) {
        atomic.StoreInt32(&sharedVar, newValue)
    }
    

选择哪种方法取决于你的具体需求和应用场景。对于简单的变量,原子操作可能是最快的选择;而对于复杂的逻辑,互斥锁和通道可能更直观和易于维护。

回到顶部