V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
caola
V2EX  ›  Go 编程语言

怎么把全局共享修改为并发的

  •  
  •   caola · 23 天前 · 1203 次点击

    有没有 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()
    }
    
    
    4 条回复    2024-11-28 09:38:59 +08:00
    E520
        1
    E520  
       23 天前
    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()
    }
    caola
        2
    caola  
    OP
       23 天前
    @E520 你这个是加锁的,不清楚是否可以应对一个请求反代花了很长的时间才结束,那么锁没有释放前是否就无法响应新的请求
    eudore
        3
    eudore  
       23 天前
    每次请求创建一次 proxyInfo 副本就好了,可以运行加上-race 参数测试并发问题。

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

    ```go
    func proxyRequestHandler() http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
    pi := &proxyInfo{}
    //不是 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)
    }
    }
    ```
    caola
        4
    caola  
    OP
       23 天前
    @eudore 你的方法我现在就在用的,但唯一的缺点是要查询两次 redis ,一次在 tls 的 GetCertificate 里面查询拿到 SSL 证书建立握手,然后再到后续 http 的 Handler 里面再查询一次。
    有没有办法在 GetCertificate 查询后拿到的数据给到 Handler 里面使用。
    如果是 http 就没有这个问题直接在 Handler 查询。现在要兼容 http 和 https 两种,希望是在 tls 的 GetCertificate 查了就不要在后面的 Handler 再查一次
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2845 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 19ms · UTC 08:47 · PVG 16:47 · LAX 00:47 · JFK 03:47
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.