]> 127.0.0.1 Git - front/.git/commitdiff
1 v0.1.20240313005357
authorqydysky <qydysky@foxmail.com>
Tue, 12 Mar 2024 17:15:00 +0000 (01:15 +0800)
committerqydysky <qydysky@foxmail.com>
Tue, 12 Mar 2024 17:15:00 +0000 (01:15 +0800)
README.md
config.go
http.go
rollRule.go [new file with mode: 0644]
ws.go

index 0a591502308fe3cbedd719e5b70c6429133a593d..0c87390c89fcde37267e0fedde8bf7b128782643 100755 (executable)
--- a/README.md
+++ b/README.md
@@ -7,47 +7,55 @@
 - 自定权重
 - 故障转移
 - 自定义头
+- 多种轮询
 - 请求头过滤
 - 请求数据过滤
 
 支持嵌入到其他项目中/独立运行
 
-配置为json数组格式[],下面为数组中的其中一个{},注意此级不会动态增加/移除
+配置为json数组格式[],下面为数组中的其中一个{},下述字段*倾斜*的,表示不会根据配置动态加载
 
 config:
 
-- addr: string 监听端口 例:0.0.0.0:8081
-- matchRule: string 匹配规则 prefix:当未匹配到时,返回最近的/匹配, all:当未匹配到时,返回404
-- copyBlocks: int 转发的块数量,默认1000
-- tls: {} 启用tls
-    - pub: string 公钥pem路径
-    - key: string 私钥pem路径
+- *addr*: string 监听端口 例:`0.0.0.0:8081`
+- *matchRule*: string 匹配规则 `prefix`:当未匹配到时,返回最近的/匹配, `all`:当未匹配到时,返回404
+- *copyBlocks*: int 转发的块数量,默认`1000`
+- *tls*: {} 启用tls
+    - *pub*: string 公钥pem路径
+    - *key*: string 私钥pem路径
 - routes: [] 路由, 可以动态增加/删除
     - path: string 路径
-    - splicing: int 当客户端支持cookie时,将会固定使用后端多少秒
     - pathAdd: bool 将客户端访问的路径附加在path上 例:/api/req => /ws => /ws/api/req
+    - rollRule: string 可选
+        - `disableC_MinFirst`(禁用数较少的优先)
+        - `dealingC_MinFirst`(连接数较少的优先)
+        - `chosenC_MinFirst`(被选择较少的优先)
+        - `lastResDur_MinFirst`(上次响应时间较短的优先)
+        - `resDur_MinFirst`(总响应时间较短的优先)
+        - (使用rand.Shuffle随机,默认)
     - dealer 将会附加到每个backs前
     - backs: [] 后端, 可以动态增加/删除
         - name: string 后端名称,将在日志中显示
-        - to: string 后端地址,例"s://www.baidu.com",会根据客户端自动添加http or ws在地址前
-        - weight: int 权重,按routes中的全部back的权重比分配,当权重为0时,将停止新请求的进入
+        - to: string 后端地址,例`s://www.baidu.com`,会根据客户端自动添加http or ws在地址前
+        - weight: int 权重,按routes中的全部back的权重比分配,当权重为0时,将停止新请求的进入
         - dealer
 
 dealer:
 
+- splicing: int 当客户端支持cookie时,将会固定使用后端多少秒,默认不启用
 - errToSec: float64 当后端响应超过(ws则指初次返回时间)指定秒,将会触发errBanSec
 - errBanSec: int 当后端错误时(指连接失败,不指后端错误响应),将会禁用若干秒
 - reqHeader: [] 请求后端前,请求头处理器, 可以动态增加/删除
-    - action: string 可选access、deny、replace、add、del、set
+    - action: string 可选`access`、`deny`、`replace`、`add`、`del`、`set`
     - key: string 具体处理哪个头
-    - matchExp: string access时不匹配将结束请求。deny时匹配将结束请求。replace时结合value进行替换
-    - value: string replace时结合matchExp进行替换。add时将附加值。set时将覆盖值。
+    - matchExp: string `access`时不匹配将结束请求。`deny`时匹配将结束请求。`replace`时结合value进行替换
+    - value: string `replace`时结合matchExp进行替换。add时将附加值。`set`时将覆盖值。
 - resHeader: [] 返回后端的响应前,请求头处理器, 可以动态增加/删除
-    - action: string 可选access、deny、add、del、set
+    - action: string 可选`access`、`deny`、`add`、`del`、`set`
     - key: string 具体处理哪个头
-    - matchExp: string access时不匹配将结束请求。deny时匹配将结束请求。replace时结合value进行替换
-    - value: string replace时结合matchExp进行替换。add时将附加值。set时将覆盖值。
+    - matchExp: string `access`时不匹配将结束请求。`deny`时匹配将结束请求。`replace`时结合value进行替换
+    - value: string `replace`时结合matchExp进行替换。`add`时将附加值。`set`时将覆盖值。
 - reqBody: [] 请求后端前,请求数据过滤器, 可以动态增加/删除
-    - action: string 可选access、deny
-    - reqSize:string 限定请求数据大小,默认为"1M"
-    - matchExp: string access时如不匹配将结束请求。deny时如匹配将结束请求。
+    - action: string 可选`access`、`deny`
+    - reqSize:string 限定请求数据大小,默认为`1M`
+    - matchExp: string `access`时如不匹配将结束请求。d`eny`时如匹配将结束请求。
index 5fd13c6c0f01563aa14ea17acadb4877c2f548f6..b36e86d49251c6be7267ce32518c58b435147c5a 100755 (executable)
--- a/config.go
+++ b/config.go
@@ -7,7 +7,6 @@ import (
        "errors"
        "fmt"
        "io"
-       "math/rand/v2"
        "net"
        "net/http"
        "regexp"
@@ -175,8 +174,8 @@ type Route struct {
        config *Config `json:"-"`
        Path   string  `json:"path"`
 
-       Splicing int  `json:"splicing"`
-       PathAdd  bool `json:"pathAdd"`
+       PathAdd  bool   `json:"pathAdd"`
+       RollRule string `json:"rollRule"`
        Dealer
 
        backMap sync.Map `json:"-"`
@@ -219,16 +218,25 @@ func (t *Route) FiliterBackByRequest(r *http.Request) []*Back {
                        }
                }
        }
-       rand.Shuffle(len(backLink), func(i, j int) {
-               backLink[i], backLink[j] = backLink[j], backLink[i]
-       })
+
+       if f, ok := rollRuleMap[t.RollRule]; ok {
+               f(backLink)
+       } else {
+               rand_Shuffle(backLink)
+       }
+
        return backLink
 }
 
 type Back struct {
-       route *Route       `json:"-"`
-       lock  sync.RWMutex `json:"-"`
-       upT   time.Time    `json:"-"`
+       route      *Route        `json:"-"`
+       lock       sync.RWMutex  `json:"-"`
+       upT        time.Time     `json:"-"`
+       disableC   int           `json:"-"`
+       dealingC   int           `json:"-"`
+       chosenC    int           `json:"-"`
+       lastResDru time.Duration `json:"-"`
+       resDru     time.Duration `json:"-"`
 
        Name   string `json:"name"`
        To     string `json:"to"`
@@ -281,6 +289,21 @@ func BodyMatchs(matchBody []Body, r *http.Request) (reader io.ReadCloser, e erro
        return
 }
 
+func (t *Back) be(opT time.Time) {
+       t.lock.Lock()
+       t.chosenC += 1
+       t.lastResDru = time.Since(opT)
+       t.resDru += t.lastResDru
+       t.dealingC += 1
+       t.lock.Unlock()
+}
+
+func (t *Back) ed() {
+       t.lock.Lock()
+       t.dealingC -= 1
+       t.lock.Unlock()
+}
+
 func (t *Back) IsLive() bool {
        t.lock.RLock()
        defer t.lock.RUnlock()
@@ -293,11 +316,13 @@ func (t *Back) Disable() {
        }
        t.lock.Lock()
        defer t.lock.Unlock()
+       t.disableC += 1
        t.upT = time.Now().Add(time.Second * time.Duration(t.ErrBanSec))
 }
 
 type Dealer struct {
        ErrToSec  float64  `json:"errToSec"`
+       Splicing  int      `json:"splicing"`
        ErrBanSec int      `json:"errBanSec"`
        ReqHeader []Header `json:"reqHeader"`
        ResHeader []Header `json:"resHeader"`
diff --git a/http.go b/http.go
index 53699da2c7ad2bec9d47866d88282a74e8a243f5..b05cd9b5021b2d0a6b6fbd2e50555bc88b3c7452 100644 (file)
--- a/http.go
+++ b/http.go
@@ -75,7 +75,14 @@ func httpDealer(ctx context.Context, w http.ResponseWriter, r *http.Request, rou
                return ErrAllBacksFail
        }
 
-       {
+       logger.Debug(`T:`, fmt.Sprintf("%v > %v > %v http ok %v", chosenBack.route.config.Addr, routePath, chosenBack.Name, time.Since(opT)))
+
+       if chosenBack.route.RollRule != `` {
+               chosenBack.be(opT)
+               defer chosenBack.ed()
+       }
+
+       if chosenBack.Splicing != 0 {
                cookie := &http.Cookie{
                        Name:   "_psign_" + cookie,
                        Value:  chosenBack.Id(),
@@ -110,7 +117,9 @@ func httpDealer(ctx context.Context, w http.ResponseWriter, r *http.Request, rou
                defer put()
                if _, e = io.CopyBuffer(w, resp.Body, tmpbuf); e != nil {
                        logger.Error(`E:`, fmt.Sprintf("%v > %v > %v http %v %v", chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT)))
-                       chosenBack.Disable()
+                       if !errors.Is(e, context.Canceled) {
+                               chosenBack.Disable()
+                       }
                        return errors.Join(ErrCopy, e)
                }
        }
diff --git a/rollRule.go b/rollRule.go
new file mode 100644 (file)
index 0000000..fd5a6fa
--- /dev/null
@@ -0,0 +1,46 @@
+package front
+
+import (
+       "math/rand/v2"
+       "slices"
+)
+
+var rollRuleMap = make(map[string]func(backLink []*Back))
+
+func init() {
+       rollRuleMap[`disable_MinFirst`] = func(backLink []*Back) {
+               slices.SortStableFunc(backLink, func(a, b *Back) int {
+                       return a.disableC/(a.Weight+1) - b.disableC/(b.Weight+1)
+               })
+       }
+
+       rollRuleMap[`dealingC_MinFirst`] = func(backLink []*Back) {
+               slices.SortStableFunc(backLink, func(a, b *Back) int {
+                       return a.dealingC/(a.Weight+1) - b.dealingC/(b.Weight+1)
+               })
+       }
+
+       rollRuleMap[`chosenC_MinFirst`] = func(backLink []*Back) {
+               slices.SortStableFunc(backLink, func(a, b *Back) int {
+                       return a.chosenC/(a.Weight+1) - b.chosenC/(b.Weight+1)
+               })
+       }
+
+       rollRuleMap[`lastResDur_MinFirst`] = func(backLink []*Back) {
+               slices.SortStableFunc(backLink, func(a, b *Back) int {
+                       return int(a.lastResDru.Milliseconds()/int64(a.Weight+1) - b.lastResDru.Milliseconds()/int64(b.Weight+1))
+               })
+       }
+
+       rollRuleMap[`resDur_MinFirst`] = func(backLink []*Back) {
+               slices.SortStableFunc(backLink, func(a, b *Back) int {
+                       return int(a.resDru.Milliseconds()/int64(a.Weight+1) - b.resDru.Milliseconds()/int64(b.Weight+1))
+               })
+       }
+}
+
+func rand_Shuffle(backLink []*Back) {
+       rand.Shuffle(len(backLink), func(i, j int) {
+               backLink[i], backLink[j] = backLink[j], backLink[i]
+       })
+}
diff --git a/ws.go b/ws.go
index b2101ccfab7c6c12012628b99bdcd41b3a9d4603..dcfdf2a95f6def0102e27ef1925d421c24828e10 100644 (file)
--- a/ws.go
+++ b/ws.go
@@ -17,6 +17,7 @@ import (
        _ "unsafe"
 
        "github.com/gorilla/websocket"
+       pctx "github.com/qydysky/part/ctx"
        pslice "github.com/qydysky/part/slice"
        "golang.org/x/net/proxy"
 )
@@ -79,9 +80,18 @@ func wsDealer(ctx context.Context, w http.ResponseWriter, r *http.Request, route
                return ErrAllBacksFail
        }
 
+       if pctx.Done(r.Context()) {
+               return context.Canceled
+       }
+
        logger.Debug(`T:`, fmt.Sprintf("%v > %v > %v ws ok %v", chosenBack.route.config.Addr, routePath, chosenBack.Name, time.Since(opT)))
 
-       {
+       if chosenBack.route.RollRule != `` {
+               chosenBack.be(opT)
+               defer chosenBack.ed()
+       }
+
+       if chosenBack.Splicing != 0 {
                cookie := &http.Cookie{
                        Name:   "_psign_" + cookie,
                        Value:  chosenBack.Id(),
@@ -112,13 +122,17 @@ func wsDealer(ctx context.Context, w http.ResponseWriter, r *http.Request, route
                select {
                case e := <-copyWsMsg(req, conn, blocksi):
                        if e != nil {
-                               chosenBack.Disable()
+                               if !errors.Is(e, context.Canceled) {
+                                       chosenBack.Disable()
+                               }
                                logger.Error(`E:`, fmt.Sprintf("%v > %v > %v ws %v %v", chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT)))
                                return errors.Join(ErrCopy, e)
                        }
                case e := <-copyWsMsg(conn, req, blocksi):
                        if e != nil {
-                               chosenBack.Disable()
+                               if !errors.Is(e, context.Canceled) {
+                                       chosenBack.Disable()
+                               }
                                logger.Error(`E:`, fmt.Sprintf("%v > %v > %v ws %v %v", chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT)))
                                return errors.Join(ErrCopy, e)
                        }