]> 127.0.0.1 Git - front/.git/commitdiff
init
authorqydysky <qydysky@foxmail.com>
Sat, 2 Dec 2023 14:51:33 +0000 (22:51 +0800)
committerqydysky <qydysky@foxmail.com>
Sat, 2 Dec 2023 14:51:33 +0000 (22:51 +0800)
config.go [new file with mode: 0644]
go.mod [new file with mode: 0644]
go.sum [new file with mode: 0644]
main.go [new file with mode: 0644]
main.json [new file with mode: 0644]

diff --git a/config.go b/config.go
new file mode 100644 (file)
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 (file)
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 (file)
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 (file)
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 (file)
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