- 自定权重
- 故障转移
- 自定义头
+- 多种轮询
- 请求头过滤
- 请求数据过滤
支持嵌入到其他项目中/独立运行
-配置为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`时如匹配将结束请求。
"errors"
"fmt"
"io"
- "math/rand/v2"
"net"
"net/http"
"regexp"
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:"-"`
}
}
}
- 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"`
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()
}
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"`
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(),
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)
}
}
--- /dev/null
+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]
+ })
+}
_ "unsafe"
"github.com/gorilla/websocket"
+ pctx "github.com/qydysky/part/ctx"
pslice "github.com/qydysky/part/slice"
"golang.org/x/net/proxy"
)
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(),
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)
}