package main import ( "crypto/tls" "fmt" "io" "io/ioutil" "log" "net" "net/http" "net/http/httputil" "net/url" "os" "path/filepath" "strings" "sync" "time" "github.com/gorilla/websocket" "gopkg.in/yaml.v2" ) // Backend结构体 var Backends []*Backend // 设置跨域请求 func SetCors(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") w.Header().Set("Access-Control-Allow-Headers", "*") w.Header().Set("Access-Control-Expose-Headers", "*") w.Header().Set("Access-Control-Allow-Credentials", "true") // 如果请求方法为 OPTIONS,直接返回 200 状态码 if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } } // 存储路径的并发安全的Map var pathdict sync.Map = sync.Map{} func main() { log.SetFlags(log.Llongfile) // 将静态资源路径存储到pathdict pathdict.Store("/css", true) pathdict.Store("/fonts", true) pathdict.Store("/img", true) pathdict.Store("/js", true) pathdict.Store("/svg", true) pathdict.Store("/favicon.ico", true) rootDir := "../server" // 更改为你的根目录 vueBuild := "/opt/fusenpack-vue-created" apiURL, err := url.Parse("http://localhost:9900") if err != nil { panic(err) } mux := http.NewServeMux() // 获取并解析服务信息 results := GetZeroInfo(rootDir) var allRoutes map[string]bool = make(map[string]bool) for _, result := range results { fmt.Printf("FolderName: %s, Host: %s, Port: %d, PrefixRoute: %v\n", result.FolderName, result.Host, result.Port, result.PrefixRoute) var routes []string for k := range result.PrefixRoute { routes = append(routes, k) allRoutes[k] = true } // 根据获取的服务信息创建后端服务 Backends = append(Backends, NewBackend(mux, fmt.Sprintf("http://%s:%d", result.Host, result.Port), routes...)) } // 定义用于服务Vue dist文件夹的静态文件服务器 fs := http.FileServer(http.Dir(vueBuild)) indexHtmlPath := vueBuild + "/index.html" mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { err := r.ParseMultipartForm(100 << 20) if err != nil { log.Println(err) } if strings.HasPrefix(r.URL.Path, "/api/") { // 对/api开头的请求进行反向代理 proxy := httputil.NewSingleHostReverseProxy(apiURL) proxy.ServeHTTP(w, r) return } else { // 根据请求路径判断是服务静态文件或者是返回index.html idx := strings.Index(r.URL.Path[1:], "/") var prefix string if idx != -1 { prefix = r.URL.Path[:idx+1] } else { prefix = r.URL.Path } if _, ok := pathdict.Load(prefix); ok { fs.ServeHTTP(w, r) } else { http.ServeFile(w, r, indexHtmlPath) } } })) ServerAddress := ":9900" log.Println("listen on ", ServerAddress) keydata, err := os.ReadFile("/opt/server.fusen.3718.cn.key") if err != nil { panic(err) } pemdata, err := os.ReadFile("/opt/server.fusen.3718.cn.pem") if err != nil { panic(err) } cert, err := tls.X509KeyPair(pemdata, keydata) if err != nil { panic(err) } tlscfg := &tls.Config{ Certificates: []tls.Certificate{cert}, MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS13, } serv := http.Server{ Addr: ServerAddress, Handler: mux, TLSConfig: tlscfg, } log.Fatal(serv.ListenAndServeTLS("", "")) } // 后端服务的类型 type Backend struct { HttpAddress string Client *http.Client Handler http.HandlerFunc Dialer *websocket.Dialer } func NewBackend(mux *http.ServeMux, httpAddress string, muxPaths ...string) *Backend { // 如果路径最后没有以'/'结尾,则添加'/' for i, muxPath := range muxPaths { if muxPath[len(muxPath)-1] != '/' { muxPath = muxPath + "/" muxPaths[i] = muxPath } } // 创建HTTP客户端,设置相关的超时参数和连接数限制 client := &http.Client{ Transport: &http.Transport{ DialContext: (&net.Dialer{ Timeout: 300 * time.Second, KeepAlive: 60 * time.Second, }).DialContext, ForceAttemptHTTP2: true, MaxIdleConns: 100, MaxIdleConnsPerHost: 100, IdleConnTimeout: 300 * time.Second, TLSHandshakeTimeout: 300 * time.Second, ExpectContinueTimeout: 1 * time.Second, }, } // 创建后端服务对象,包含地址和客户端 backend := &Backend{ HttpAddress: httpAddress, Client: client, } // 创建处理请求的函数 handleRequest := func(w http.ResponseWriter, r *http.Request) { if websocket.IsWebSocketUpgrade(r) { //todo: 建立websocket的代理 target := url.URL{Scheme: "ws", Host: strings.Split(backend.HttpAddress, "//")[1], Path: r.URL.Path} var transfer = func(src, dest *websocket.Conn) { for { mType, msg, err := src.ReadMessage() if err != nil { log.Println(err) break } err = dest.WriteMessage(mType, msg) if err != nil { log.Println(err) break } } src.Close() dest.Close() } header := r.Header.Clone() // log.Println(target.String()) header.Del("Sec-Websocket-Extensions") header.Del("Upgrade") header.Del("Sec-Websocket-Key") header.Del("Sec-Websocket-Version") header.Del("Connection") // header.Del("Origin") proxyConn, _, err := backend.Dialer.Dial(target.String(), header) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // defer proxyConn.Close() upgrader := websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } conn, err := upgrader.Upgrade(w, r, nil) if err != nil { return } // defer conn.Close() go transfer(proxyConn, conn) // go transfer(conn, proxyConn) return } // 解析目标URL,包含了查询参数 targetURL, err := url.Parse(httpAddress + r.URL.String()) if err != nil { http.Error(w, "Error parsing target URL", http.StatusInternalServerError) return } // 创建新的请求 proxyReq, err := http.NewRequest(r.Method, targetURL.String(), r.Body) if err != nil { http.Error(w, "Error creating proxy request", http.StatusInternalServerError) return } // 复制原始请求的 Header for key, values := range r.Header { for _, value := range values { proxyReq.Header.Add(key, value) } } // 设置 Content-Length 和 Content-Type proxyReq.ContentLength = r.ContentLength proxyReq.Header.Set("Content-Type", r.Header.Get("Content-Type")) // 发送请求 resp, err := backend.Client.Do(proxyReq) if err != nil { http.Error(w, "Error sending proxy request", http.StatusInternalServerError) return } defer resp.Body.Close() // 复制目标服务器的响应 Header for key, values := range resp.Header { for _, value := range values { w.Header().Add(key, value) } } // 转发目标服务器的响应状态码和主体 w.WriteHeader(resp.StatusCode) _, err = io.Copy(w, resp.Body) if err != nil { http.Error(w, "Error copying proxy response", http.StatusInternalServerError) return } } // 为每个路径注册处理函数 for _, muxPath := range muxPaths { mux.HandleFunc(muxPath, handleRequest) } // 返回后端服务对象 return backend } // get_zero_info.go // Config 结构体用于解析yaml配置文件 type Config struct { Host string `yaml:"Host"` Port int `yaml:"Port"` } // Result 结构体用于存储解析结果 type Result struct { FolderName string Host string Port int PrefixRoute map[string]bool } // GetZeroInfo 遍历指定目录,并解析相关信息 func GetZeroInfo(rootDir string) (results []*Result) { entries, err := ioutil.ReadDir(rootDir) if err != nil { log.Fatal(err) } for _, entry := range entries { // 只处理目录类型 if entry.IsDir() { result, err := findFoldersAndExtractInfo(rootDir, entry) if err != nil { log.Fatal(err) } results = append(results, result) } } return } // findFoldersAndExtractInfo 查找目录并提取信息 func findFoldersAndExtractInfo(rootDir string, entry os.FileInfo) (*Result, error) { var result *Result folderName := entry.Name() path := filepath.Join(rootDir, folderName) err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error { if err != nil { return err } // 跳过非目录类型 if !info.IsDir() { return nil } relPath, err := filepath.Rel(path, path) if err != nil { return err } // 跳过非当前目录的子目录 if strings.Contains(relPath, string(os.PathSeparator)) { return filepath.SkipDir } // 读取配置文件 configPath := filepath.Join(path, "etc", folderName+".yaml") routesPath := filepath.Join(path, "internal", "handler", "routes.go") configContent, err := ioutil.ReadFile(configPath) if err != nil { return err } var config Config err = yaml.Unmarshal(configContent, &config) if err != nil { return err } // 读取路由文件 routesContent, err := ioutil.ReadFile(routesPath) if err != nil { return err } PrefixRoute := extractPrefixRouteValues(string(routesContent)) // 构建结果 result = &Result{ FolderName: folderName, Host: config.Host, Port: config.Port, PrefixRoute: PrefixRoute, } return filepath.SkipDir }) if err != nil { return nil, err } return result, nil } // extractPrefixRouteValues 提取路由前缀 func extractPrefixRouteValues(content string) map[string]bool { lines := strings.Split(content, "\n") var prefixPath map[string]bool = make(map[string]bool) for _, line := range lines { // 查找包含 "Path:" 的行 if strings.Contains(line, "Path:") { path := strings.TrimSpace(strings.TrimPrefix(line, "Path:")) paths := strings.Split(strings.Trim(path, `"`), "/") path1 := "/" + paths[1] + "/" + paths[2] if _, ok := prefixPath[path1]; !ok { prefixPath[path1] = true } } } return prefixPath }