fusenapi/proxyserver/main.go
2023-08-10 16:44:45 +08:00

393 lines
9.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"fmt"
"io"
"io/fs"
"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() {
// 将静态资源路径存储到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) {
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)
log.Fatal(http.ListenAndServe(ServerAddress, mux))
}
// 后端服务的类型
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: 60 * time.Second,
KeepAlive: 60 * time.Second,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
},
}
dialer := &websocket.Dialer{
Proxy: http.ProxyFromEnvironment,
NetDial: func(network, addr string) (net.Conn, error) {
return net.Dial(network, addr)
},
}
// 创建后端服务对象,包含地址和客户端
backend := &Backend{
HttpAddress: httpAddress,
Client: client,
Dialer: dialer,
}
// 创建处理请求的函数
handleRequest := func(w http.ResponseWriter, r *http.Request) {
if websocket.IsWebSocketUpgrade(r) {
// Handle websocket connections
handleWebSocketProxy(w, r, backend)
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 := os.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 fs.DirEntry) (*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 := os.ReadFile(configPath)
if err != nil {
return err
}
var config Config
err = yaml.Unmarshal(configContent, &config)
if err != nil {
return err
}
// 读取路由文件
routesContent, err := os.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
}
func handleWebSocketProxy(w http.ResponseWriter, r *http.Request, backend *Backend) {
target := url.URL{Scheme: "ws", Host: backend.HttpAddress, Path: r.URL.Path}
proxyConn, _, err := backend.Dialer.DialContext(r.Context(), target.String(), nil)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer proxyConn.Close()
upgrader := websocket.Upgrader{}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
defer conn.Close()
go transfer(proxyConn, conn)
go transfer(conn, proxyConn)
}
func transfer(src, dest *websocket.Conn) {
for {
messageType, data, err := src.ReadMessage()
if err != nil {
break
}
err = dest.WriteMessage(messageType, data)
if err != nil {
break
}
}
src.Close()
dest.Close()
}