From: qydysky Date: Tue, 12 Mar 2024 17:15:00 +0000 (+0800) Subject: 1 X-Git-Tag: v0.1.20240313005357 X-Git-Url: http://127.0.0.1:8081/?a=commitdiff_plain;h=78d0a6cb7d81781cfb3b9ea61851068f11f76430;p=front%2F.git 1 --- diff --git a/README.md b/README.md index 0a59150..0c87390 100755 --- 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`时如匹配将结束请求。 diff --git a/config.go b/config.go index 5fd13c6..b36e86d 100755 --- 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 53699da..b05cd9b 100644 --- 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 index 0000000..fd5a6fa --- /dev/null +++ b/rollRule.go @@ -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 b2101cc..dcfdf2a 100644 --- 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) }