From: qydysky Date: Sun, 28 May 2023 04:04:41 +0000 (+0800) Subject: Add Web服务连接限制 X-Git-Tag: v0.9.8~3 X-Git-Url: http://127.0.0.1:8081/?a=commitdiff_plain;h=2d79d7923b50b22a4f5f19efc4d70261027a0f84;p=bili_danmu%2F.git Add Web服务连接限制 --- diff --git a/CV/Var.go b/CV/Var.go index aa2956b..ab934f9 100644 --- a/CV/Var.go +++ b/CV/Var.go @@ -67,6 +67,7 @@ type Common struct { Danmu_Main_mq *mq.Msgq //消息 ReqPool *pool.Buf[reqf.Req] //请求池 SerF *web.WebPath //web服务处理 + SerLimit *web.Limits //Web服务连接限制 StartT time.Time //启动时间 } @@ -145,6 +146,7 @@ func (t *Common) Copy() *Common { Danmu_Main_mq: t.Danmu_Main_mq, ReqPool: t.ReqPool, SerF: t.SerF, + SerLimit: t.SerLimit, StartT: t.StartT, } @@ -292,6 +294,7 @@ func (t *Common) Init() *Common { t.SerF = new(web.WebPath) t.Stream_url = &url.URL{} + t.SerLimit = &web.Limits{} if serAdress, ok := t.K_v.LoadV("Web服务地址").(string); ok && serAdress != "" { serUrl, e := url.Parse("http://" + serAdress) @@ -303,8 +306,38 @@ func (t *Common) Init() *Common { Addr: serUrl.Host, }, t.SerF) + if limits, ok := t.K_v.LoadV(`Web服务连接限制`).([]any); ok { + for i := 0; i < len(limits); i++ { + if vm, ok := limits[i].(map[string]any); ok { + if cidr, ok := vm["cidr"].(string); !ok { + continue + } else if max, ok := vm["max"].(float64); !ok { + continue + } else { + t.SerLimit.AddLimitItem(web.NewLimitItem(int(max)).Cidr(cidr)) + } + } + } + } + if val, ok := t.K_v.LoadV("性能路径").(string); ok && val != "" { - t.SerF.Store(val, func(w http.ResponseWriter, _ *http.Request) { + var cache web.Cache + t.SerF.Store(val, func(w http.ResponseWriter, r *http.Request) { + //limit + if t.SerLimit.AddCount(r) { + web.WithStatusCode(w, http.StatusTooManyRequests) + return + } + + //cache + if bp, ok := cache.IsCache(val); ok { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Cache-Control", "max-age=5") + _, _ = w.Write(*bp) + return + } + w = cache.Cache(val, time.Second*5, w) + var memStats runtime.MemStats runtime.ReadMemStats(&memStats) @@ -465,7 +498,7 @@ type ResStruct struct { Data any `json:"data"` } -func (t ResStruct) Write(w http.ResponseWriter) { +func (t ResStruct) Write(w http.ResponseWriter) []byte { w.Header().Set("Content-Type", "application/json") data, e := json.Marshal(t) if e != nil { @@ -475,4 +508,5 @@ func (t ResStruct) Write(w http.ResponseWriter) { data, _ = json.Marshal(t) } _, _ = w.Write(data) + return data } diff --git a/F/api.go b/F/api.go index c6b5784..386ec18 100644 --- a/F/api.go +++ b/F/api.go @@ -26,6 +26,7 @@ import ( limit "github.com/qydysky/part/limit" reqf "github.com/qydysky/part/reqf" psync "github.com/qydysky/part/sync" + web "github.com/qydysky/part/web" "github.com/mdp/qrterminal/v3" qr "github.com/skip2/go-qrcode" @@ -1340,7 +1341,12 @@ func (c *GetFunc) Get_cookie() (missKey []string) { defer os.RemoveAll(`qr.png`) //启动web if scanPath, ok := c.K_v.LoadV("扫码登录路径").(string); ok && scanPath != "" { - c.SerF.Store(scanPath, func(w http.ResponseWriter, _ *http.Request) { + c.SerF.Store(scanPath, func(w http.ResponseWriter, r *http.Request) { + //limit + if c.SerLimit.AddCount(r) { + web.WithStatusCode(w, http.StatusTooManyRequests) + return + } _ = file.New("qr.png", 0, true).CopyToIoWriter(w, humanize.MByte, true) }) if c.K_v.LoadV(`扫码登录自动打开标签页`).(bool) { diff --git a/Reply/F.go b/Reply/F.go index e2e1607..3d31e10 100644 --- a/Reply/F.go +++ b/Reply/F.go @@ -1148,15 +1148,50 @@ func init() { // debug模式 if de, ok := c.C.K_v.LoadV(`debug模式`).(bool); ok && de { - c.C.SerF.Store("/debug/pprof/", pprof.Index) - c.C.SerF.Store("/debug/pprof/cmdline", pprof.Cmdline) - c.C.SerF.Store("/debug/pprof/profile", pprof.Profile) - c.C.SerF.Store("/debug/pprof/symbol", pprof.Symbol) - c.C.SerF.Store("/debug/pprof/trace", pprof.Trace) + c.C.SerF.Store("/debug/pprof/", func(w http.ResponseWriter, r *http.Request) { + //limit + if c.C.SerLimit.AddCount(r) { + pweb.WithStatusCode(w, http.StatusTooManyRequests) + return + } + pprof.Index(w, r) + }) + c.C.SerF.Store("/debug/pprof/cmdline", func(w http.ResponseWriter, r *http.Request) { + //limit + if c.C.SerLimit.AddCount(r) { + pweb.WithStatusCode(w, http.StatusTooManyRequests) + return + } + pprof.Cmdline(w, r) + }) + c.C.SerF.Store("/debug/pprof/profile", func(w http.ResponseWriter, r *http.Request) { + //limit + if c.C.SerLimit.AddCount(r) { + pweb.WithStatusCode(w, http.StatusTooManyRequests) + return + } + pprof.Profile(w, r) + }) + c.C.SerF.Store("/debug/pprof/symbol", func(w http.ResponseWriter, r *http.Request) { + //limit + if c.C.SerLimit.AddCount(r) { + pweb.WithStatusCode(w, http.StatusTooManyRequests) + return + } + pprof.Symbol(w, r) + }) + c.C.SerF.Store("/debug/pprof/trace", func(w http.ResponseWriter, r *http.Request) { + //limit + if c.C.SerLimit.AddCount(r) { + pweb.WithStatusCode(w, http.StatusTooManyRequests) + return + } + pprof.Trace(w, r) + }) } // 直播流回放连接限制 - var climit pweb.CountLimits + var climit pweb.Limits if limits, ok := c.C.K_v.LoadV(`直播流回放连接限制`).([]any); ok { for i := 0; i < len(limits); i++ { if vm, ok := limits[i].(map[string]any); ok { @@ -1165,24 +1200,28 @@ func init() { } else if max, ok := vm["max"].(float64); !ok { continue } else { - climit.SetMaxCount(cidr, int(max)) + climit.AddLimitItem(pweb.NewLimitItem(int(max)).Cidr(cidr)) } } } } + // cache + var cache pweb.Cache + // 直播流主页 c.C.SerF.Store(path, func(w http.ResponseWriter, r *http.Request) { + //limit + if c.C.SerLimit.AddCount(r) { + pweb.WithStatusCode(w, http.StatusTooManyRequests) + return + } + p := strings.TrimPrefix(r.URL.Path, path) + if len(p) == 0 || p[len(p)-1] == '/' { p += "index.html" } - f := file.New("html/streamList/"+p, 0, true) - if !f.IsExist() || f.IsDir() { - w.WriteHeader(http.StatusNotFound) - return - } - if strings.HasSuffix(p, ".js") { w.Header().Set("content-type", "application/javascript") } else if strings.HasSuffix(p, ".css") { @@ -1190,11 +1229,42 @@ func init() { } else if strings.HasSuffix(p, ".html") { w.Header().Set("content-type", "text/html") } - _ = f.CopyToIoWriter(w, humanize.MByte, true) + + //cache + if bp, ok := cache.IsCache("html/streamList/" + p); ok { + w.Header().Set("Cache-Control", "max-age=60") + _, _ = w.Write(*bp) + return + } + w = cache.Cache("html/streamList/"+p, time.Minute, w) + + f := file.New("html/streamList/"+p, 0, true) + if !f.IsExist() || f.IsDir() { + w.WriteHeader(http.StatusNotFound) + return + } + + b, _ := f.ReadAll(humanize.KByte, humanize.MByte) + _, _ = w.Write(b) }) // 直播流文件列表api - c.C.SerF.Store(path+"filePath", func(w http.ResponseWriter, _ *http.Request) { + c.C.SerF.Store(path+"filePath", func(w http.ResponseWriter, r *http.Request) { + //limit + if c.C.SerLimit.AddCount(r) { + pweb.WithStatusCode(w, http.StatusTooManyRequests) + return + } + + //cache + if bp, ok := cache.IsCache(path + "filePath"); ok { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Cache-Control", "max-age=5") + _, _ = w.Write(*bp) + return + } + w = cache.Cache(path+"filePath", time.Second*5, w) + if v, ok := c.C.K_v.LoadV(`直播流保存位置`).(string); ok && v != "" { type dirEntryDirs []fs.DirEntry var list dirEntryDirs @@ -1246,10 +1316,16 @@ func init() { // 直播流播放器 c.C.SerF.Store(path+"player/", func(w http.ResponseWriter, r *http.Request) { + //limit + if c.C.SerLimit.AddCount(r) { + pweb.WithStatusCode(w, http.StatusTooManyRequests) + return + } + // 直播流回放连接限制 if climit.ReachMax(r) { w.WriteHeader(http.StatusTooManyRequests) - _, _ = w.Write([]byte("已达到设定最大连接数")) + _, _ = w.Write([]byte(http.StatusText(http.StatusTooManyRequests))) return } @@ -1257,11 +1333,6 @@ func init() { if len(p) == 0 || p[len(p)-1] == '/' { p += "index.html" } - f := file.New("html/artPlayer/"+p, 0, true) - if !f.IsExist() { - w.WriteHeader(http.StatusNotFound) - return - } if strings.HasSuffix(p, ".js") { w.Header().Set("content-type", "application/javascript") @@ -1270,11 +1341,33 @@ func init() { } else if strings.HasSuffix(p, ".html") { w.Header().Set("content-type", "text/html") } - _ = f.CopyToIoWriter(w, humanize.MByte, true) + + //cache + if bp, ok := cache.IsCache("html/artPlayer/" + p); ok { + w.Header().Set("Cache-Control", "max-age=60") + _, _ = w.Write(*bp) + return + } + w = cache.Cache("html/artPlayer/"+p, time.Minute, w) + + f := file.New("html/artPlayer/"+p, 0, true) + if !f.IsExist() { + w.WriteHeader(http.StatusNotFound) + return + } + + b, _ := f.ReadAll(humanize.KByte, humanize.MByte) + _, _ = w.Write(b) }) // 流地址 c.C.SerF.Store(path+"stream", func(w http.ResponseWriter, r *http.Request) { + //limit + if c.C.SerLimit.AddCount(r) { + pweb.WithStatusCode(w, http.StatusTooManyRequests) + return + } + // 直播流回放连接限制 if climit.AddCount(r) { w.WriteHeader(http.StatusTooManyRequests) @@ -1399,6 +1492,19 @@ func init() { // 弹幕回放 c.C.SerF.Store(path+"player/ws", func(w http.ResponseWriter, r *http.Request) { + //limit + if c.C.SerLimit.AddCount(r) { + pweb.WithStatusCode(w, http.StatusTooManyRequests) + return + } + + // 直播流回放连接限制 + if climit.ReachMax(r) { + w.WriteHeader(http.StatusTooManyRequests) + _, _ = w.Write([]byte(http.StatusText(http.StatusTooManyRequests))) + return + } + var rpath string if qref := r.URL.Query().Get("ref"); rpath == "" && qref != "" { diff --git a/demo/config/config_K_v.json b/demo/config/config_K_v.json index 9355cdb..f4e86dc 100644 --- a/demo/config/config_K_v.json +++ b/demo/config/config_K_v.json @@ -90,6 +90,13 @@ ], "Web服务地址-help":"填写本程序各组件所用的服务地址 例0.0.0.0:10000 为空时不启动Web服务", "Web服务地址":"0.0.0.0:10000", + "Web服务连接限制-help": "限制回放连接数,<0无限制,=0禁止,>0最大数量", + "Web服务连接限制": [ + { + "cidr":"0.0.0.0/0", + "max":-1 + } + ], "直播Web服务路径":"/web/", "直播Web可以发送弹幕":true, "弹幕回放-help": "仅保存当前直播间流为true时才有效", diff --git a/go.mod b/go.mod index e6bf7a3..ba299da 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/gotk3/gotk3 v0.6.2 github.com/mdp/qrterminal/v3 v3.0.0 - github.com/qydysky/part v0.27.12 + github.com/qydysky/part v0.27.16 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 golang.org/x/text v0.9.0 diff --git a/go.sum b/go.sum index f83e64a..f3add4f 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,8 @@ github.com/mdp/qrterminal/v3 v3.0.0/go.mod h1:NJpfAs7OAm77Dy8EkWrtE4aq+cE6McoLXl github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI= github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/qydysky/part v0.27.12 h1:IGVfRuiYYhfaIwlTYZL4QqFNJw7uF0l6uQcaI4Ajnso= -github.com/qydysky/part v0.27.12/go.mod h1:IEMpGB0NBl6MklZmoenSpS5ChhaIL79JYFo6mF1UkAU= +github.com/qydysky/part v0.27.16 h1:RJ254afpd3qtTDE5/E4M8pt/XHWp6L5Cr/BYUj7Ai4w= +github.com/qydysky/part v0.27.16/go.mod h1:IEMpGB0NBl6MklZmoenSpS5ChhaIL79JYFo6mF1UkAU= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=