From: qydysky Date: Sat, 2 Dec 2023 14:51:33 +0000 (+0800) Subject: init X-Git-Tag: v0.0.20231202150522~5 X-Git-Url: http://127.0.0.1:8081/?a=commitdiff_plain;h=94576701165a177105c27b5c85a750ac348b0e46;p=front%2F.git init --- 94576701165a177105c27b5c85a750ac348b0e46 diff --git a/config.go b/config.go new file mode 100644 index 0000000..a1b75ee --- /dev/null +++ b/config.go @@ -0,0 +1,62 @@ +package main + +import ( + "crypto/md5" + "encoding/json" + "fmt" + "sync" +) + +type Config struct { + lock sync.RWMutex + Addr string `json:"addr"` + MatchRule string `json:"matchRule"` + Routes []Route `json:"routes"` +} + +type Route struct { + Path string `json:"path"` + Sign string `json:"-"` + Back []Back `json:"back"` +} + +func (t *Route) SwapSign() bool { + data, _ := json.Marshal(t) + w := md5.New() + w.Write(data) + sign := fmt.Sprintf("%x", w.Sum(nil)) + if t.Sign != sign { + t.Sign = sign + return true + } + return false +} + +func (t *Route) GenBack() []*Back { + var backLink []*Back + for _, back := range t.Back { + tmpBack := Back{ + To: back.To, + Weight: back.Weight, + ReqHeader: append([]Header{}, back.ReqHeader...), + ResHeader: append([]Header{}, back.ResHeader...), + } + for i := 1; i <= back.Weight; i++ { + backLink = append(backLink, &tmpBack) + } + } + return backLink +} + +type Back struct { + To string `json:"to"` + Weight int `json:"weight"` + ReqHeader []Header `json:"reqHeader"` + ResHeader []Header `json:"resHeader"` +} + +type Header struct { + Action string `json:"action"` + Key string `json:"key"` + Value string `json:"value"` +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0db8234 --- /dev/null +++ b/go.mod @@ -0,0 +1,24 @@ +module github.com/qydysky/front + +go 1.21.4 + +require ( + github.com/andybalholm/brotli v1.0.6 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/klauspost/compress v1.17.3 // indirect + github.com/miekg/dns v1.1.57 // indirect + github.com/qydysky/part v0.28.20231202144738 // indirect + github.com/shirou/gopsutil v3.21.11+incompatible // indirect + github.com/thedevsaddam/gojsonq/v2 v2.5.2 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.18.0 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.15.0 // indirect +) + +replace github.com/qydysky/part => ../part diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..93bb9eb --- /dev/null +++ b/go.sum @@ -0,0 +1,63 @@ +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= +github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= +github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA= +github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI= +github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= +github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= +github.com/qydysky/part v0.27.17 h1:hYSOjnkV0jIf/tSLBmqEAXfkprEhwySQrVWRtrZxuXE= +github.com/qydysky/part v0.27.17/go.mod h1:IEMpGB0NBl6MklZmoenSpS5ChhaIL79JYFo6mF1UkAU= +github.com/qydysky/part v0.28.1 h1:VkG9hnN7176v55InlxVFzD4Pxac5X4oHfG5Z7DBwgkk= +github.com/qydysky/part v0.28.1/go.mod h1:NyKyjpBCSjcHtKlC+fL5lCidm57UCnwEgufiBDs5yxA= +github.com/qydysky/part v0.28.20231202005014 h1:/txeAjH2FdMPPX1BfcLtd5ArWAG1DcP/Rg4CeT73EjE= +github.com/qydysky/part v0.28.20231202005014/go.mod h1:NyKyjpBCSjcHtKlC+fL5lCidm57UCnwEgufiBDs5yxA= +github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= +github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/thedevsaddam/gojsonq/v2 v2.5.2 h1:CoMVaYyKFsVj6TjU6APqAhAvC07hTI6IQen8PHzHYY0= +github.com/thedevsaddam/gojsonq/v2 v2.5.2/go.mod h1:bv6Xa7kWy82uT0LnXPE2SzGqTj33TAEeR560MdJkiXs= +github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= +github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= +github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= +github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= +golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= +golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= +golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= diff --git a/main.go b/main.go new file mode 100644 index 0000000..757a70f --- /dev/null +++ b/main.go @@ -0,0 +1,283 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "flag" + "fmt" + "io" + "net/http" + "os" + "os/signal" + "slices" + "time" + + pctx "github.com/qydysky/part/ctx" + pfile "github.com/qydysky/part/file" + plog "github.com/qydysky/part/log" + psys "github.com/qydysky/part/sys" + pweb "github.com/qydysky/part/web" +) + +func main() { + // 保持唤醒 + var stop = psys.Sys().PreventSleep() + defer stop.Done() + + // 获取config路径 + configP := flag.String("c", "main.json", "config") + testP := flag.Int("t", 0, "test port") + _ = flag.Bool("q", true, "no warn,error log") + flag.Parse() + + // 日志初始化 + logger := plog.New(plog.Config{ + Stdout: true, + Prefix_string: map[string]struct{}{ + `T:`: plog.On, + `I:`: plog.On, + `W:`: plog.On, + `E:`: plog.On, + }, + }) + + if slices.Contains(os.Args[1:], "-q") { + logger.L(`I:`, "不输出警告") + delete(logger.Config.Prefix_string, `E:`) + delete(logger.Config.Prefix_string, `W:`) + } + + // 根ctx + ctx, cancle := pctx.WithWait(context.Background(), 0, time.Minute*2) + + // 获取config + configS := Config{} + configF := pfile.New(*configP, 0, true) + if !configF.IsExist() { + logger.L(`E:`, "配置不存在") + return + } + defer configF.Close() + + var buf = make([]byte, 1<<16) + + if e := loadConfig(buf, configF, &configS, logger); e != nil { + logger.L(`E:`, "配置加载", e) + } + + // 定时加载 + go LoadPeriod(ctx, buf, configF, &configS, logger) + + // 测试响应 + go Test(ctx, *testP, logger) + + go Run(ctx, &configS, logger) + + // ctrl+c退出 + var interrupt = make(chan os.Signal, 2) + signal.Notify(interrupt, os.Interrupt) + <-interrupt + if errors.Is(cancle(), pctx.ErrWaitTo) { + logger.L(`E:`, "退出超时") + } +} + +// 定时加载 +func LoadPeriod(ctx context.Context, buf []byte, configF *pfile.File, configS *Config, logger *plog.Log_interface) { + ctx1, done1 := pctx.WaitCtx(ctx) + defer done1() + // 定时加载config + for { + select { + case <-time.After(time.Second * 10): + if e := loadConfig(buf, configF, configS, logger); e != nil { + logger.L(`E:`, "配置加载", e) + } + case <-ctx1.Done(): + return + } + } +} + +// 测试 +func Test(ctx context.Context, port int, logger *plog.Log_interface) { + if port == 0 { + return + } + logger = logger.Base("测试") + ctx1, done1 := pctx.WaitCtx(ctx) + defer done1() + logger.L(`I:`, "启动", fmt.Sprintf("127.0.0.1:%d", port)) + s := pweb.New(&http.Server{ + Addr: fmt.Sprintf("127.0.0.1:%d", port), + WriteTimeout: time.Second * time.Duration(10), + }) + defer s.Shutdown() + s.Handle(map[string]func(http.ResponseWriter, *http.Request){ + `/`: func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte("ok")) + }, + }) + <-ctx1.Done() +} + +// 转发 +func Run(ctx context.Context, configSP *Config, logger *plog.Log_interface) { + logger = logger.Base("转发") + // 根ctx + ctx, cancle := pctx.WithWait(ctx, 0, time.Minute) + defer func() { + if errors.Is(cancle(), pctx.ErrWaitTo) { + logger.L(`E:`, "退出超时") + } + }() + + // 路由 + routeP := pweb.WebPath{} + + logger.L(`I:`, "启动...") + defer logger.L(`I:`, "退出...") + + // config对象初次加载 + if e := applyConfig(ctx, configSP, &routeP, logger); e != nil { + return + } + + // matchfunc + var matchfunc func(path string) (func(w http.ResponseWriter, r *http.Request), bool) + switch configSP.MatchRule { + case "prefix": + logger.L(`I:`, "匹配规则", "prefix") + matchfunc = routeP.LoadPerfix + case "all": + logger.L(`I:`, "匹配规则", "all") + matchfunc = routeP.Load + default: + logger.L(`E:`, "匹配规则", "无效") + return + } + + syncWeb := pweb.NewSyncMap(&http.Server{ + Addr: configSP.Addr, + }, &routeP, matchfunc) + defer syncWeb.Shutdown() + + // 定时加载config + for { + select { + case <-time.After(time.Second * 10): + _ = applyConfig(ctx, configSP, &routeP, logger) + case <-ctx.Done(): + return + } + } +} + +func loadConfig(buf []byte, configF *pfile.File, configS *Config, logger *plog.Log_interface) error { + if i, e := configF.Read(buf); e != nil && !errors.Is(e, io.EOF) { + logger.L(`E:`, `读取配置`, e) + return e + } else if i == cap(buf) { + logger.L(`E:`, `读取配置`, `buf full`) + return errors.New(`buf full`) + } else { + configS.lock.Lock() + defer configS.lock.Unlock() + if e := json.Unmarshal(buf[:i], configS); e != nil { + logger.L(`E:`, `读取配置`, e) + return e + } + } + return nil +} + +func applyConfig(ctx context.Context, configS *Config, routeP *pweb.WebPath, logger *plog.Log_interface) error { + configS.lock.RLock() + defer configS.lock.RUnlock() + + for i := 0; i < len(configS.Routes); i++ { + route := &configS.Routes[i] + + if !route.SwapSign() { + continue + } + + if len(route.Back) == 0 { + logger.L(`I:`, "移除路由", route.Path) + routeP.Store(route.Path, nil) + continue + } + + backArray := route.GenBack() + + if len(backArray) == 0 { + logger.L(`I:`, "移除路由", route.Path) + routeP.Store(route.Path, nil) + continue + } + + logger.L(`I:`, "路由更新", route.Path) + + routeP.Store(route.Path, func(w http.ResponseWriter, r *http.Request) { + ctx1, done1 := pctx.WaitCtx(ctx) + defer done1() + + back := backArray[time.Now().UnixMilli()%int64(len(backArray))] + + req, e := http.NewRequestWithContext(ctx1, r.Method, back.To+r.URL.String(), r.Body) + if e != nil { + pweb.WithStatusCode(w, http.StatusServiceUnavailable) + logger.L(`E:`, fmt.Sprintf("%s=>%s %v", route.Path, back.To, e)) + return + } + + for k, v := range r.Header { + req.Header.Set(k, v[0]) + } + + for _, v := range back.ReqHeader { + switch v.Action { + case `set`: + req.Header.Set(v.Key, v.Value) + case `add`: + req.Header.Add(v.Key, v.Value) + case `del`: + req.Header.Del(v.Key) + default: + logger.L(`W:`, fmt.Sprintf("%s=>%s 无效ReqHeader %v", route.Path, back.To, v)) + } + } + + resp, e := http.DefaultClient.Do(req) + if e != nil { + pweb.WithStatusCode(w, http.StatusServiceUnavailable) + logger.L(`E:`, fmt.Sprintf("%s=>%s %v", route.Path, back.To, e)) + return + } + + for k, v := range resp.Header { + w.Header().Set(k, v[0]) + } + + for _, v := range back.ResHeader { + switch v.Action { + case `set`: + w.Header().Set(v.Key, v.Value) + case `add`: + w.Header().Add(v.Key, v.Value) + case `del`: + w.Header().Del(v.Key) + default: + logger.L(`W:`, fmt.Sprintf("%s=>%s 无效ResHeader %v", route.Path, back.To, v)) + } + } + + w.WriteHeader(resp.StatusCode) + + _, _ = io.Copy(w, resp.Body) + resp.Body.Close() + }) + } + return nil +} diff --git a/main.json b/main.json new file mode 100644 index 0000000..92e68c8 --- /dev/null +++ b/main.json @@ -0,0 +1,22 @@ +{ + "addr":"0.0.0.0:9009", + "matchRule": "prefix", + "routes":[ + { + "path": "/", + "back": [ + { + "to": "http://127.0.0.1:13000", + "weight": 1, + "resHeader":[ + { + "action": "set", + "key": "KEY", + "value": "asf" + } + ] + } + ] + } + ] +} \ No newline at end of file