--- /dev/null
+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=
--- /dev/null
+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
+}