From: qydysky Date: Thu, 21 Mar 2024 07:05:01 +0000 (+0800) Subject: 1 X-Git-Tag: v0.1.20240321070814 X-Git-Url: http://127.0.0.1:8081/?a=commitdiff_plain;h=eed53f7be1163324ee0aeade9a5971bb8dc40a39;p=front%2F.git 1 --- diff --git a/README.md b/README.md index 1ba4e2f..56164a3 100755 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ config: - *tls*: {} 启用tls - *pub*: string 公钥pem路径 - *key*: string 私钥pem路径 -- routes: [] 路由, 可以动态增加/删除 +- routes: [] 路由 - path: string 路径 - pathAdd: bool 将客户端访问的路径附加在path上 例:/api/req => /ws => /ws/api/req - rollRule: string 可选 @@ -34,32 +34,48 @@ config: - `lastResDur_MinFirst`(上次响应时间较短的优先) - `resDur_MinFirst`(总响应时间较短的优先) - (使用rand.Shuffle随机,默认) - - dealer 将会附加到每个backs前 - - backs: [] 后端, 可以动态增加/删除 + - reqBody: 请求后端前,请求数据过滤器 + - action: string 可选`access`、`deny`。 + - reqSize:string 限定请求数据大小,默认为`1M` + - matchExp: string `access`时如不匹配将结束请求。`deny`时如匹配将结束请求。 + - setting... 将会给backs默认值 + - backs: [] 后端 - name: string 后端名称,将在日志中显示 - to: string 后端地址,例`s://www.baidu.com`,会根据客户端自动添加http or ws在地址前 - weight: int 权重,按routes中的全部back的权重比分配,当权重为0时,将停止新请求的进入 - - dealer + - setting... -dealer: +setting: - splicing: int 当客户端支持cookie时,将会固定使用后端多少秒,默认不启用 - errToSec: float64 当后端响应超过(ws则指初次返回时间)指定秒,将会触发errBanSec - errBanSec: int 当后端错误时(指连接失败,不指后端错误响应),将会禁用若干秒 -- reqPather: [] 请求后端前,请求路径过滤器, 可以动态增加/删除 - - action: string 可选`access`、`deny`。 - - matchExp: string `access`时如不匹配将结束请求。`deny`时如匹配将结束请求。 -- reqHeader: [] 请求后端前,请求头处理器, 可以动态增加/删除 - - action: string 可选`access`、`deny`、`replace`、`add`、`del`、`set`。 - - key: string 具体处理哪个头 - - matchExp: string `access`时不匹配将结束请求。`deny`时匹配将结束请求。`replace`时结合value进行替换 - - value: string `replace`时结合matchExp进行替换。add时将附加值。`set`时将覆盖值。 -- resHeader: [] 返回后端的响应前,请求头处理器, 可以动态增加/删除 - - action: string 可选`access`、`deny`、`add`、`del`、`set`。 - - key: string 具体处理哪个头 - - matchExp: string `access`时不匹配将结束请求。`deny`时匹配将结束请求。`replace`时结合value进行替换 - - value: string `replace`时结合matchExp进行替换。`add`时将附加值。`set`时将覆盖值。 -- reqBody: [] 请求后端前,请求数据过滤器, 可以动态增加/删除 - - action: string 可选`access`、`deny`。 - - reqSize:string 限定请求数据大小,默认为`1M` - - matchExp: string `access`时如不匹配将结束请求。`deny`时如匹配将结束请求。 + +- filiter: + - reqUri: 请求后端前,请求路径过滤器 + - accessRule: 布尔表达式,为true时才通过 + - items: map[string]string + - id: matchExp + - reqHeader: 请求后端前,请求头处理器 + - accessRule: 布尔表达式,为true时才通过 + - items: map[string]{} + - id: + - key: string header头 + - matchExp: string + - resHeader: 返回后端的响应前,请求头处理器 + - accessRule: 布尔表达式,为true时才通过 + - items: map[string]{} + - id: + - key: string header头 + - matchExp: string +- dealer: + - reqHeader:[] 请求后端前,请求头处理器 + - action: string 可选`replace`、`add`、`del`、`set`。 + - key: string 具体处理哪个头 + - matchExp: string `replace`时结合value进行替换 + - value: string `replace`时结合matchExp进行替换。add时将附加值。`set`时将覆盖值。 + - resHeader:[] 返回后端的响应前,请求头处理器 + - action: string 可选`add`、`del`、`set`。 + - key: string 具体处理哪个头 + - matchExp: string `replace`时结合value进行替换 + - value: string `replace`时结合matchExp进行替换。`add`时将附加值。`set`时将覆盖值。 diff --git a/config.go b/config.go index fde08db..2df1001 100755 --- a/config.go +++ b/config.go @@ -1,22 +1,20 @@ package front import ( - "bytes" "context" "crypto/tls" "errors" "fmt" - "io" "net" "net/http" - "regexp" + "os" "strings" "sync" "time" - "github.com/dustin/go-humanize" + "github.com/qydysky/front/dealer" + filiter "github.com/qydysky/front/filiter" pctx "github.com/qydysky/part/ctx" - pio "github.com/qydysky/part/io" pslice "github.com/qydysky/part/slice" pweb "github.com/qydysky/part/web" ) @@ -39,7 +37,9 @@ type Config struct { func (t *Config) Run(ctx context.Context, logger Logger) { ctx, done := pctx.WithWait(ctx, 0, time.Minute) - defer done() + defer func() { + _ = done() + }() var matchfunc func(path string) (func(w http.ResponseWriter, r *http.Request), bool) switch t.MatchRule { @@ -85,28 +85,64 @@ func (t *Config) SwapSign(ctx context.Context, logger Logger) { logger.Info(`I:`, fmt.Sprintf("%v > %v", t.Addr, k)) t.routeMap.Store(k, route) + var logFormat = "%v%v %v %v" + t.routeP.Store(route.Path, func(w http.ResponseWriter, r *http.Request) { - if !PatherMatchs(route.ReqPather, r) { - logger.Warn(`W:`, fmt.Sprintf("%v > %v %v %v", route.config.Addr, route.Path, r.RequestURI, ErrPatherCheckFail)) + if len(r.RequestURI) > 8000 { + logger.Warn(`W:`, fmt.Sprintf(logFormat, route.config.Addr, route.Path, "BLOCK", ErrUriTooLong)) + w.Header().Add(header+"Error", ErrUriTooLong.Error()) + w.WriteHeader(http.StatusBadRequest) + return + } + + if ok, e := route.Filiter.ReqUri.Match(r); e != nil { + logger.Warn(`W:`, fmt.Sprintf(logFormat, route.config.Addr, route.Path, "Err", e)) + } else if !ok { + logger.Warn(`W:`, fmt.Sprintf(logFormat, route.config.Addr, route.Path, "BLOCK", ErrPatherCheckFail)) w.Header().Add(header+"Error", ErrPatherCheckFail.Error()) w.WriteHeader(http.StatusForbidden) return } - if !HeaderMatchs(route.ReqHeader, r) { - logger.Warn(`W:`, fmt.Sprintf("%v > %v %v %v", route.config.Addr, route.Path, r.RequestURI, ErrHeaderCheckFail)) + if ok, e := route.Filiter.ReqHeader.Match(r.Header); e != nil { + logger.Warn(`W:`, fmt.Sprintf(logFormat, route.config.Addr, route.Path, "Err", e)) + } else if !ok { + logger.Warn(`W:`, fmt.Sprintf(logFormat, route.config.Addr, route.Path, "BLOCK", ErrHeaderCheckFail)) w.Header().Add(header+"Error", ErrHeaderCheckFail.Error()) w.WriteHeader(http.StatusForbidden) return } + if ok, e := route.ReqBody.Match(r); e != nil { + logger.Warn(`W:`, fmt.Sprintf(logFormat, route.config.Addr, route.Path, "Err", e)) + } else if !ok { + logger.Warn(`W:`, fmt.Sprintf(logFormat, route.config.Addr, route.Path, "BLOCK", ErrBodyCheckFail)) + w.Header().Add(header+"Error", ErrBodyCheckFail.Error()) + w.WriteHeader(http.StatusForbidden) + return + } + var backIs []*Back if t, e := r.Cookie("_psign_" + cookie); e == nil { - if backP, ok := route.backMap.Load(t.Value); ok && HeaderMatchs(backP.(*Back).ReqHeader, r) { - backP.(*Back).cloneDealer() - for i := 0; i < backP.(*Back).Weight; i++ { - backIs = append(backIs, backP.(*Back)) + if backP, aok := route.backMap.Load(t.Value); aok { + + if ok, e := backP.(*Back).getFiliterReqUri().Match(r); e != nil { + logger.Warn(`W:`, fmt.Sprintf(logFormat, route.config.Addr, route.Path, "Err", e)) + } else if ok { + aok = false + } + + if ok, e := backP.(*Back).getFiliterReqHeader().Match(r.Header); e != nil { + logger.Warn(`W:`, fmt.Sprintf(logFormat, route.config.Addr, route.Path, "Err", e)) + } else if ok { + aok = false + } + + if aok { + for i := 0; i < backP.(*Back).Weight; i++ { + backIs = append(backIs, backP.(*Back)) + } } } } @@ -114,7 +150,7 @@ func (t *Config) SwapSign(ctx context.Context, logger Logger) { backIs = append(backIs, route.FiliterBackByRequest(r)...) if len(backIs) == 0 { - logger.Warn(`W:`, fmt.Sprintf("%v > %v %v %v", route.config.Addr, route.Path, r.RequestURI, ErrNoRoute)) + logger.Warn(`W:`, fmt.Sprintf(logFormat, route.config.Addr, route.Path, "BLOCK", ErrNoRoute)) w.Header().Add(header+"Error", ErrNoRoute.Error()) w.WriteHeader(http.StatusNotFound) return @@ -130,6 +166,9 @@ func (t *Config) SwapSign(ctx context.Context, logger Logger) { w.Header().Add(header+"Error", e.Error()) if errors.Is(e, ErrHeaderCheckFail) || errors.Is(e, ErrBodyCheckFail) { w.WriteHeader(http.StatusForbidden) + } else if errors.Is(e, ErrAllBacksFail) { + w.WriteHeader(http.StatusBadGateway) + os.Exit(0) } else { t.routeP.GetConn(r).Close() } @@ -137,7 +176,7 @@ func (t *Config) SwapSign(ctx context.Context, logger Logger) { }) } - var del = func(k string, route *Route, logger Logger) { + var del = func(k string, _ *Route, logger Logger) { logger.Info(`I:`, fmt.Sprintf("%v x %v", t.Addr, k)) t.routeMap.Delete(k) t.routeP.Store(k, nil) @@ -184,9 +223,10 @@ type Route struct { config *Config `json:"-"` Path string `json:"path"` - PathAdd bool `json:"pathAdd"` - RollRule string `json:"rollRule"` - Dealer + PathAdd bool `json:"pathAdd"` + RollRule string `json:"rollRule"` + ReqBody filiter.Body `json:"reqBody"` + Setting backMap sync.Map `json:"-"` Backs []Back `json:"backs"` @@ -221,8 +261,8 @@ func (t *Route) SwapSign(add func(string, *Back), del func(string, *Back), logge func (t *Route) FiliterBackByRequest(r *http.Request) []*Back { var backLink []*Back for i := 0; i < len(t.Backs); i++ { - if HeaderMatchs(t.Backs[i].ReqHeader, r) { - t.Backs[i].cloneDealer() + if ok, e := t.Backs[i].getFiliterReqHeader().Match(r.Header); ok && e == nil { + t.Backs[i].route = t for k := 0; k < t.Backs[i].Weight; k++ { backLink = append(backLink, &t.Backs[i]) } @@ -252,62 +292,53 @@ type Back struct { To string `json:"to"` Weight int `json:"weight"` - Splicing int `json:"-"` - PathAdd bool `json:"-"` - Dealer - tmp Dealer `json:"-"` + Setting } -func (t *Back) cloneDealer() { - t.PathAdd = t.route.PathAdd - if t.Splicing == 0 { - t.Splicing = t.route.Splicing - } +func (t *Back) Splicing() int { + return t.route.Splicing +} +func (t *Back) PathAdd() bool { + return t.route.PathAdd +} +func (t *Back) getErrBanSec() int { if t.ErrBanSec == 0 { - t.ErrBanSec = t.route.ErrBanSec + return t.route.ErrBanSec + } else { + return t.ErrBanSec } +} +func (t *Back) getErrToSec() float64 { if t.ErrToSec == 0 { - t.ErrToSec = t.route.ErrToSec + return t.route.ErrToSec + } else { + return t.ErrToSec } - t.tmp.ReqPather = append(t.route.ReqPather, t.ReqPather...) - t.tmp.ReqHeader = append(t.route.ReqHeader, t.ReqHeader...) - t.tmp.ResHeader = append(t.route.ResHeader, t.ResHeader...) - t.tmp.ReqBody = append(t.route.ReqBody, t.ReqBody...) } - -func (t *Back) Id() string { - return fmt.Sprintf("%p", t) +func (t *Back) getFiliterReqHeader() *filiter.Header { + if !t.Filiter.ReqHeader.Valid() { + return &t.route.Filiter.ReqHeader + } else { + return &t.Filiter.ReqHeader + } } - -func PatherMatchs(matchPath []Header, r *http.Request) bool { - matchs := len(matchPath) - 1 - for ; matchs >= 0; matchs -= 1 { - if !matchPath[matchs].Match(r.RequestURI) { - break - } +func (t *Back) getFiliterReqUri() *filiter.Uri { + if !t.Filiter.ReqUri.Valid() { + return &t.route.Filiter.ReqUri + } else { + return &t.Filiter.ReqUri } - return matchs == -1 } - -func HeaderMatchs(matchHeader []Header, r *http.Request) bool { - matchs := len(matchHeader) - 1 - for ; matchs >= 0; matchs -= 1 { - if !matchHeader[matchs].Match(r.Header.Get(matchHeader[matchs].Key)) { - break - } +func (t *Back) getFiliterResHeader() *filiter.Header { + if !t.Filiter.ResHeader.Valid() { + return &t.route.Filiter.ResHeader + } else { + return &t.Filiter.ResHeader } - return matchs == -1 } -func BodyMatchs(matchBody []Body, r *http.Request) (reader io.ReadCloser, e error) { - reader = r.Body - for i := 0; i < len(matchBody); i++ { - reader, e = matchBody[i].Match(reader) - if e != nil { - return - } - } - return +func (t *Back) Id() string { + return fmt.Sprintf("%p", t) } func (t *Back) be(opT time.Time) { @@ -332,94 +363,20 @@ func (t *Back) IsLive() bool { } func (t *Back) Disable() { - if t.ErrBanSec == 0 { - t.ErrBanSec = 1 + tmp := t.getErrBanSec() + if tmp == 0 { + tmp = 1 } 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"` - ReqPather []Header `json:"reqPather"` - ReqHeader []Header `json:"reqHeader"` - ResHeader []Header `json:"resHeader"` - ReqBody []Body `json:"reqBody"` -} - -type Header struct { - Action string `json:"action"` - Key string `json:"key"` - MatchExp string `json:"matchExp"` - Value string `json:"value"` -} - -func (t *Header) Match(value string) bool { - if t.Action != "access" && t.Action != "deny" { - return true - } - if t.MatchExp != "" { - if exp, e := regexp.Compile(t.MatchExp); e != nil || !exp.MatchString(value) { - return t.Action == "deny" - } - } - return t.Action == "access" -} - -type Body struct { - Action string `json:"action"` - ReqSize string `json:"reqSize"` - MatchExp string `json:"matchExp"` + t.upT = time.Now().Add(time.Second * time.Duration(tmp)) } -func (t *Body) Match(r io.ReadCloser) (d io.ReadCloser, err error) { - if exp, e := regexp.Compile(t.MatchExp); e == nil { - if t.ReqSize == "" { - t.ReqSize = "1M" - } - - var ( - size, err = humanize.ParseBytes(t.ReqSize) - buf = make([]byte, size) - n int - ) - - if err != nil { - return nil, err - } - - for n < int(size) && err == nil { - var nn int - nn, err = r.Read(buf[n:]) - n += nn - } - if n >= int(size) { - return nil, errors.New("body overflow") - } else if err != nil && !errors.Is(err, io.EOF) { - return nil, err - } - buf = buf[:n] - - switch t.Action { - case "access": - if !exp.Match(buf) { - return nil, errors.New("body deny") - } - case "deny": - if exp.Match(buf) { - return nil, errors.New("body deny") - } - } - - return pio.RWC{ - R: bytes.NewReader(buf).Read, - C: func() error { return nil }, - }, nil - } else { - return nil, e - } +type Setting struct { + ErrToSec float64 `json:"errToSec"` + Splicing int `json:"splicing"` + ErrBanSec int `json:"errBanSec"` + Filiter filiter.Filiter `json:"filiter"` + Dealer dealer.Dealer `json:"dealer"` } diff --git a/dealer/dealer.go b/dealer/dealer.go new file mode 100644 index 0000000..e6e02a7 --- /dev/null +++ b/dealer/dealer.go @@ -0,0 +1,6 @@ +package dealer + +type Dealer struct { + ReqHeader []HeaderDealer `json:"reqHeader"` + ResHeader []HeaderDealer `json:"resHeader"` +} diff --git a/dealer/header.go b/dealer/header.go new file mode 100644 index 0000000..98bd0af --- /dev/null +++ b/dealer/header.go @@ -0,0 +1,9 @@ +package dealer + +import "github.com/qydysky/front/filiter" + +type HeaderDealer struct { + filiter.HeaderFiliter + Action string `json:"action"` + Value string `json:"value"` +} diff --git a/filiter/body.go b/filiter/body.go new file mode 100644 index 0000000..d79c76b --- /dev/null +++ b/filiter/body.go @@ -0,0 +1,83 @@ +package filiter + +import ( + "bytes" + "errors" + "io" + "net/http" + "regexp" + + "github.com/dustin/go-humanize" + pio "github.com/qydysky/part/io" +) + +type Body struct { + Action string `json:"action"` + ReqSize string `json:"reqSize"` + MatchExp string `json:"matchExp"` +} + +func (t *Body) Valid() bool { + return t.MatchExp != "" +} + +func (t *Body) Match(r *http.Request) (ok bool, err error) { + if !t.Valid() { + return true, nil + } + if exp, e := regexp.Compile(t.MatchExp); e == nil { + if t.ReqSize == "" { + t.ReqSize = "1M" + } + + var ( + size, err = humanize.ParseBytes(t.ReqSize) + buf = make([]byte, size) + n int + ) + + if err != nil { + return false, err + } + + for n < int(size) && err == nil { + var nn int + nn, err = r.Body.Read(buf[n:]) + n += nn + } + + if n >= int(size) { + r.Body = pio.RWC{ + R: io.MultiReader(bytes.NewReader(buf), r.Body).Read, + C: r.Body.Close, + } + return false, errors.New("body overflow") + } else if err != nil && !errors.Is(err, io.EOF) { + r.Body.Close() + return false, err + } + buf = buf[:n] + + switch t.Action { + case "access": + if !exp.Match(buf) { + r.Body.Close() + return false, nil + } + case "deny": + if exp.Match(buf) { + r.Body.Close() + return false, nil + } + } + + r.Body = pio.RWC{ + R: bytes.NewReader(buf).Read, + C: r.Body.Close, + } + + return true, nil + } else { + return false, e + } +} diff --git a/filiter/filiter.go b/filiter/filiter.go new file mode 100644 index 0000000..b18d8a2 --- /dev/null +++ b/filiter/filiter.go @@ -0,0 +1,7 @@ +package filiter + +type Filiter struct { + ReqHeader Header `json:"reqHeader"` + ReqUri Uri `json:"reqUri"` + ResHeader Header `json:"resHeader"` +} diff --git a/filiter/header.go b/filiter/header.go new file mode 100644 index 0000000..c80411e --- /dev/null +++ b/filiter/header.go @@ -0,0 +1,44 @@ +package filiter + +import ( + "net/http" + "regexp" + + boolS "github.com/qydysky/part/boolS" +) + +type Header struct { + AccessRule string `json:"accessRule"` + Items map[string]HeaderFiliter `json:"items"` +} + +func (t *Header) Valid() bool { + return t.AccessRule != "" && len(t.Items) != 0 +} + +func (t *Header) Match(h http.Header) (bool, error) { + if !t.Valid() { + return true, nil + } + m := map[string]func() bool{} + for k, v := range t.Items { + m[k] = func() bool { return v.Match(h) } + } + return boolS.New(t.AccessRule, m).Check() +} + +type HeaderFiliter struct { + Key string `json:"key"` + MatchExp string `json:"matchExp"` +} + +func (t *HeaderFiliter) Match(h http.Header) bool { + if t.MatchExp != "" { + if exp, e := regexp.Compile(t.MatchExp); e != nil { + return false + } else { + return exp.MatchString(h.Get(t.Key)) + } + } + return true +} diff --git a/filiter/uri.go b/filiter/uri.go new file mode 100644 index 0000000..3bc57a7 --- /dev/null +++ b/filiter/uri.go @@ -0,0 +1,36 @@ +package filiter + +import ( + "log" + "net/http" + "regexp" + + boolS "github.com/qydysky/part/boolS" +) + +type Uri struct { + AccessRule string `json:"accessRule"` + Items map[string]string `json:"items"` +} + +func (t *Uri) Valid() bool { + return t.AccessRule != "" && len(t.Items) != 0 +} + +func (t *Uri) Match(r *http.Request) (bool, error) { + if !t.Valid() { + return true, nil + } + m := map[string]func() bool{} + for k, v := range t.Items { + m[k] = func() bool { + if exp, e := regexp.Compile(v); e != nil { + log.Default().Println(e) + return false + } else { + return exp.MatchString(r.RequestURI) + } + } + } + return boolS.New(t.AccessRule, m).Check() +} diff --git a/go.mod b/go.mod index 1479a0c..222efe8 100755 --- a/go.mod +++ b/go.mod @@ -5,20 +5,22 @@ go 1.22 require ( github.com/dustin/go-humanize v1.0.1 github.com/gorilla/websocket v1.5.1 - github.com/qydysky/part v0.28.20240310094912 - golang.org/x/net v0.18.0 + github.com/qydysky/part v0.28.20240321070228 + golang.org/x/net v0.22.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/google/uuid v1.4.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect - github.com/tklauser/go-sysconf v0.3.12 // indirect - github.com/tklauser/numcpus v0.6.1 // indirect - github.com/yusufpapurcu/wmi v1.2.3 // indirect - golang.org/x/sys v0.14.0 // indirect + github.com/tklauser/go-sysconf v0.3.13 // indirect + github.com/tklauser/numcpus v0.7.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +// replace github.com/qydysky/part => ../part diff --git a/go.sum b/go.sum index f649799..fc7659b 100755 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= -github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -7,65 +7,55 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m 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/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/qydysky/part v0.28.20240310094912 h1:n4pKPS3q6NmVkI76n447a/UM3XWXRAaLpZDzOgAo798= -github.com/qydysky/part v0.28.20240310094912/go.mod h1:8Y4MrasGC0BLEM71QY/MuP2jl+v5b0Y+rqox3qJu97c= +github.com/qydysky/part v0.28.20240321070228 h1:IGVEukobwFJ4Y629X82xmXJT+FLYeBqPh8peuoY51C4= +github.com/qydysky/part v0.28.20240321070228/go.mod h1:XytV5dI1Y7+qvjhsa2TMvi55RBZQQf0LCDYQ1kUCYqM= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 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/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -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.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -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.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= +github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= +github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= +github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 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.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/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.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.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= -golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= -lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= -modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= -modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0= -modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI= -modernc.org/libc v1.34.4 h1:r9+5s4wNeoCsB8CuJE67UB4N07ernbvrcry9O3MLWtQ= -modernc.org/libc v1.34.4/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= +modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8= +modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.45.0 h1:qmAJZf9tYFqK/SFSFqpBc9uHWGsvoYWtRcMQdG+JEfM= +modernc.org/libc v1.45.0/go.mod h1:YkRHLoN4L70OdO1cVmM83KZhRbRvsc3XogfVzbTXBwE= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= -modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.27.0 h1:MpKAHoyYB7xqcwnUwkuD+npwEa0fojF0B5QRbN+auJ8= -modernc.org/sqlite v1.27.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= +modernc.org/sqlite v1.29.5 h1:8l/SQKAjDtZFo9lkJLdk8g9JEOeYRG4/ghStDCCTiTE= +modernc.org/sqlite v1.29.5/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/http.go b/http.go index e49f0c0..d3b6d27 100644 --- a/http.go +++ b/http.go @@ -18,6 +18,7 @@ func httpDealer(ctx context.Context, w http.ResponseWriter, r *http.Request, rou opT = time.Now() resp *http.Response chosenBack *Back + logFormat = "%v%v > %v http %v %v %v" ) for 0 < len(backs) && resp == nil { @@ -29,31 +30,20 @@ func httpDealer(ctx context.Context, w http.ResponseWriter, r *http.Request, rou } url := chosenBack.To - if chosenBack.PathAdd { + if chosenBack.PathAdd() { url += r.RequestURI } url = "http" + url - if !PatherMatchs(chosenBack.ReqPather, r) { - logger.Warn(`W:`, fmt.Sprintf("%v > %v > %v http %v %v", chosenBack.route.config.Addr, routePath, chosenBack.Name, ErrPatherCheckFail, time.Since(opT))) - return ErrPatherCheckFail - } - - reader, e := BodyMatchs(chosenBack.tmp.ReqBody, r) - if e != nil { - logger.Warn(`W:`, fmt.Sprintf("%v > %v > %v http %v %v", chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT))) - return ErrBodyCheckFail - } - - req, e := http.NewRequestWithContext(ctx, r.Method, url, reader) + req, e := http.NewRequestWithContext(ctx, r.Method, url, r.Body) if e != nil { return ErrReqCreFail } - if e := copyHeader(r.Header, req.Header, chosenBack.tmp.ReqHeader); e != nil { - logger.Warn(`W:`, fmt.Sprintf("%v > %v > %v http %v %v", chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT))) - return e + if e := copyHeader(r.Header, req.Header, chosenBack.Setting.Dealer.ReqHeader); e != nil { + logger.Warn(`W:`, fmt.Sprintf(logFormat, chosenBack.route.config.Addr, routePath, chosenBack.Name, "BLOCK", e, time.Since(opT))) + return ErrDealReqHeader } client := http.Client{ @@ -63,35 +53,44 @@ func httpDealer(ctx context.Context, w http.ResponseWriter, r *http.Request, rou } resp, e = client.Do(req) if e != nil && !errors.Is(e, ErrRedirect) && !errors.Is(e, context.Canceled) { - logger.Warn(`W:`, fmt.Sprintf("%v > %v > %v http %v %v", chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT))) + logger.Warn(`W:`, fmt.Sprintf(logFormat, chosenBack.route.config.Addr, routePath, chosenBack.Name, "BLOCK", e, time.Since(opT))) chosenBack.Disable() resp = nil } - if chosenBack.ErrToSec != 0 && time.Since(opT).Seconds() > chosenBack.ErrToSec { - logger.Warn(`W:`, fmt.Sprintf("%v > %v > %v http 超时响应 %v", chosenBack.route.config.Addr, routePath, chosenBack.Name, time.Since(opT))) + if chosenBack.getErrToSec() != 0 && time.Since(opT).Seconds() > chosenBack.getErrToSec() { + logger.Warn(`W:`, fmt.Sprintf(logFormat, chosenBack.route.config.Addr, routePath, chosenBack.Name, "BLOCK", ErrResTO, time.Since(opT))) chosenBack.Disable() resp = nil } } if resp == nil { - logger.Warn(`W:`, fmt.Sprintf("%v > %v > %v http %v %v", chosenBack.route.config.Addr, routePath, chosenBack.Name, ErrAllBacksFail, time.Since(opT))) + logger.Warn(`W:`, fmt.Sprintf(logFormat, chosenBack.route.config.Addr, routePath, chosenBack.Name, "BLOCK", ErrAllBacksFail, time.Since(opT))) return ErrAllBacksFail } - logger.Debug(`T:`, fmt.Sprintf("%v > %v > %v http ok %v", chosenBack.route.config.Addr, routePath, chosenBack.Name, time.Since(opT))) + if ok, e := chosenBack.getFiliterResHeader().Match(resp.Header); e != nil { + logger.Warn(`W:`, fmt.Sprintf(logFormat, chosenBack.route.config.Addr, routePath, chosenBack.Name, "Err", e, time.Since(opT))) + } else if !ok { + logger.Warn(`W:`, fmt.Sprintf(logFormat, chosenBack.route.config.Addr, routePath, chosenBack.Name, "BLOCK", ErrHeaderCheckFail, time.Since(opT))) + w.Header().Add(header+"Error", ErrHeaderCheckFail.Error()) + w.WriteHeader(http.StatusForbidden) + return ErrHeaderCheckFail + } + + logger.Debug(`T:`, fmt.Sprintf(logFormat, chosenBack.route.config.Addr, routePath, chosenBack.Name, r.Method, r.RequestURI, time.Since(opT))) if chosenBack.route.RollRule != `` { chosenBack.be(opT) defer chosenBack.ed() } - if chosenBack.Splicing != 0 { + if chosenBack.Splicing() != 0 { cookie := &http.Cookie{ Name: "_psign_" + cookie, Value: chosenBack.Id(), - MaxAge: chosenBack.Splicing, + MaxAge: chosenBack.Splicing(), Path: "/", } if validCookieDomain(r.Host) { @@ -102,9 +101,9 @@ func httpDealer(ctx context.Context, w http.ResponseWriter, r *http.Request, rou w.Header().Add(header+"Info", cookie+";"+chosenBack.Name) - if e := copyHeader(resp.Header, w.Header(), chosenBack.tmp.ResHeader); e != nil { - logger.Warn(`W:`, fmt.Sprintf("%v > %v > %v http %v %v", chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT))) - return e + if e := copyHeader(resp.Header, w.Header(), chosenBack.Setting.Dealer.ResHeader); e != nil { + logger.Warn(`W:`, fmt.Sprintf(logFormat, chosenBack.route.config.Addr, routePath, chosenBack.Name, "BLOCK", e, time.Since(opT))) + return ErrDealResHeader } w.WriteHeader(resp.StatusCode) @@ -115,13 +114,13 @@ func httpDealer(ctx context.Context, w http.ResponseWriter, r *http.Request, rou defer resp.Body.Close() if tmpbuf, put, e := blocksi.Get(); e != nil { - logger.Error(`E:`, fmt.Sprintf("%v > %v > %v http %v %v", chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT))) + logger.Error(`E:`, fmt.Sprintf(logFormat, chosenBack.route.config.Addr, routePath, chosenBack.Name, "BLOCK", e, time.Since(opT))) chosenBack.Disable() return ErrCopy } else { 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))) + logger.Error(`E:`, fmt.Sprintf(logFormat, chosenBack.route.config.Addr, routePath, chosenBack.Name, "BLOCK", e, time.Since(opT))) if !errors.Is(e, context.Canceled) { chosenBack.Disable() } diff --git a/main.go b/main.go index 25c0710..cacde3d 100755 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ import ( "time" _ "unsafe" + "github.com/qydysky/front/dealer" pctx "github.com/qydysky/part/ctx" pweb "github.com/qydysky/part/web" ) @@ -78,9 +79,21 @@ func Test(ctx context.Context, port int, logger Logger) { 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")) + `/`: func(w http.ResponseWriter, r *http.Request) { + if strings.ToLower((r.Header.Get("Upgrade"))) == "websocket" { + conn, _ := Upgrade(w, r, http.Header{ + "Upgrade": []string{"websocket"}, + "Connection": []string{"upgrade"}, + "Sec-Websocket-Accept": []string{computeAcceptKey(r.Header.Get("Sec-WebSocket-Key"))}, + }) + conn.Close() + } else { + _, _ = io.Copy(io.Discard, r.Body) + r.Body.Close() + _, _ = w.Write([]byte("ok")) + } }, }) <-ctx1.Done() @@ -114,7 +127,7 @@ func loadConfig(ctx context.Context, buf []byte, configF File, configS *[]Config return md5k, nil } -func copyHeader(s, t http.Header, app []Header) error { +func copyHeader(s, t http.Header, app []dealer.HeaderDealer) error { sm := (map[string][]string)(s) tm := (map[string][]string)(t) for k, v := range sm { @@ -136,20 +149,6 @@ func copyHeader(s, t http.Header, app []Header) error { } for _, v := range app { switch v.Action { - case `deny`: - if va := t.Get(v.Key); va != "" { - if exp, e := regexp.Compile(v.MatchExp); e == nil && exp.MatchString(va) { - return ErrHeaderCheckFail - } - } - case `access`: - if va := t.Get(v.Key); va != "" { - if exp, e := regexp.Compile(v.MatchExp); e != nil || !exp.MatchString(va) { - return ErrHeaderCheckFail - } - } else { - return ErrHeaderCheckFail - } case `replace`: if va := t.Get(v.Key); va != "" { t.Set(v.Key, regexp.MustCompile(v.MatchExp).ReplaceAllString(va, v.Value)) @@ -176,10 +175,14 @@ var ( ErrReqCreFail = errors.New("ErrReqCreFail") ErrReqDoFail = errors.New("ErrReqDoFail") ErrResDoFail = errors.New("ErrResDoFail") + ErrResTO = errors.New("ErrResTO") + ErrUriTooLong = errors.New("ErrUriTooLong") ErrPatherCheckFail = errors.New("ErrPatherCheckFail") ErrHeaderCheckFail = errors.New("ErrHeaderCheckFail") ErrBodyCheckFail = errors.New("ErrBodyCheckFail") ErrAllBacksFail = errors.New("ErrAllBacksFail") ErrBackFail = errors.New("ErrBackFail") ErrNoRoute = errors.New("ErrNoRoute") + ErrDealReqHeader = errors.New("ErrDealReqHeader") + ErrDealResHeader = errors.New("ErrDealResHeader") ) diff --git a/ws.go b/ws.go index 3dd9900..39d1d85 100644 --- a/ws.go +++ b/ws.go @@ -26,8 +26,10 @@ func wsDealer(ctx context.Context, w http.ResponseWriter, r *http.Request, route var ( opT = time.Now() resp *http.Response + e error conn net.Conn chosenBack *Back + errFormat = "%v > %v > %v ws %v %v" ) for 0 < len(backs) && (resp == nil || conn == nil) { @@ -38,19 +40,8 @@ func wsDealer(ctx context.Context, w http.ResponseWriter, r *http.Request, route continue } - if !PatherMatchs(chosenBack.ReqPather, r) { - logger.Warn(`W:`, fmt.Sprintf("%v > %v > %v ws %v %v", chosenBack.route.config.Addr, routePath, chosenBack.Name, ErrPatherCheckFail, time.Since(opT))) - return ErrPatherCheckFail - } - - _, e := BodyMatchs(chosenBack.tmp.ReqBody, r) - if e != nil { - logger.Warn(`W:`, fmt.Sprintf("%v > %v > %v ws %v %v", chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT))) - return ErrBodyCheckFail - } - url := chosenBack.To - if chosenBack.PathAdd { + if chosenBack.PathAdd() { url += r.RequestURI } @@ -58,21 +49,21 @@ func wsDealer(ctx context.Context, w http.ResponseWriter, r *http.Request, route reqHeader := make(http.Header) - if e := copyHeader(r.Header, reqHeader, chosenBack.tmp.ReqHeader); e != nil { - logger.Warn(`W:`, fmt.Sprintf("%v > %v > %v ws %v %v", chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT))) - return e + if e := copyHeader(r.Header, reqHeader, chosenBack.Setting.Dealer.ReqHeader); e != nil { + logger.Warn(`W:`, fmt.Sprintf(errFormat, chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT))) + return ErrDealReqHeader } conn, resp, e = DialContext(ctx, url, reqHeader) if e != nil && !errors.Is(e, context.Canceled) { - logger.Warn(`W:`, fmt.Sprintf("%v > %v > %v ws %v %v", chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT))) + logger.Warn(`W:`, fmt.Sprintf(errFormat, chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT))) chosenBack.Disable() conn = nil resp = nil } - if chosenBack.ErrToSec != 0 && time.Since(opT).Seconds() > chosenBack.ErrToSec { - logger.Warn(`W:`, fmt.Sprintf("%v > %v > %v ws 超时响应 %v", chosenBack.route.config.Addr, routePath, chosenBack.Name, time.Since(opT))) + if chosenBack.getErrToSec() != 0 && time.Since(opT).Seconds() > chosenBack.getErrToSec() { + logger.Warn(`W:`, fmt.Sprintf(errFormat, chosenBack.route.config.Addr, routePath, chosenBack.Name, ErrResTO, time.Since(opT))) chosenBack.Disable() conn.Close() conn = nil @@ -81,7 +72,7 @@ func wsDealer(ctx context.Context, w http.ResponseWriter, r *http.Request, route } if resp == nil || conn == nil { - logger.Warn(`W:`, fmt.Sprintf("%v > %v > %v ws %v %v", chosenBack.route.config.Addr, routePath, chosenBack.Name, ErrBackFail, time.Since(opT))) + logger.Warn(`W:`, fmt.Sprintf(errFormat, chosenBack.route.config.Addr, routePath, chosenBack.Name, ErrBackFail, time.Since(opT))) return ErrAllBacksFail } @@ -89,6 +80,15 @@ func wsDealer(ctx context.Context, w http.ResponseWriter, r *http.Request, route return context.Canceled } + if ok, e := chosenBack.getFiliterResHeader().Match(resp.Header); e != nil { + logger.Warn(`W:`, fmt.Sprintf(errFormat, chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT))) + } else if !ok { + logger.Warn(`W:`, fmt.Sprintf(errFormat, chosenBack.route.config.Addr, routePath, chosenBack.Name, ErrHeaderCheckFail, time.Since(opT))) + w.Header().Add(header+"Error", ErrHeaderCheckFail.Error()) + w.WriteHeader(http.StatusForbidden) + return ErrHeaderCheckFail + } + 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 != `` { @@ -96,11 +96,11 @@ func wsDealer(ctx context.Context, w http.ResponseWriter, r *http.Request, route defer chosenBack.ed() } - if chosenBack.Splicing != 0 { + if chosenBack.Splicing() != 0 { cookie := &http.Cookie{ Name: "_psign_" + cookie, Value: chosenBack.Id(), - MaxAge: chosenBack.Splicing, + MaxAge: chosenBack.Splicing(), Path: "/", } if validCookieDomain(r.Host) { @@ -114,9 +114,9 @@ func wsDealer(ctx context.Context, w http.ResponseWriter, r *http.Request, route defer conn.Close() resHeader := make(http.Header) - if e := copyHeader(resp.Header, resHeader, chosenBack.tmp.ResHeader); e != nil { - logger.Warn(`W:`, fmt.Sprintf("%v > %v > %v ws %v %v", chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT))) - return e + if e := copyHeader(resp.Header, resHeader, chosenBack.Setting.Dealer.ResHeader); e != nil { + logger.Warn(`W:`, fmt.Sprintf(errFormat, chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT))) + return ErrDealResHeader } if req, e := Upgrade(w, r, resHeader); e != nil { @@ -130,7 +130,7 @@ func wsDealer(ctx context.Context, w http.ResponseWriter, r *http.Request, route 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))) + logger.Error(`E:`, fmt.Sprintf(errFormat, chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT))) return ErrCopy } case e := <-copyWsMsg(conn, req, blocksi): @@ -138,7 +138,7 @@ func wsDealer(ctx context.Context, w http.ResponseWriter, r *http.Request, route 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))) + logger.Error(`E:`, fmt.Sprintf(errFormat, chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT))) return ErrCopy } case <-ctx.Done(): @@ -319,16 +319,13 @@ func DialContext(ctx context.Context, urlStr string, requestHeader http.Header) } } - var br *bufio.Reader - if br == nil { - if d.ReadBufferSize == 0 { - d.ReadBufferSize = defaultReadBufferSize - } else if d.ReadBufferSize < maxControlFramePayloadSize { - // must be large enough for control frame - d.ReadBufferSize = maxControlFramePayloadSize - } - br = bufio.NewReaderSize(netConn, d.ReadBufferSize) + if d.ReadBufferSize == 0 { + d.ReadBufferSize = defaultReadBufferSize + } else if d.ReadBufferSize < maxControlFramePayloadSize { + // must be large enough for control frame + d.ReadBufferSize = maxControlFramePayloadSize } + br := bufio.NewReaderSize(netConn, d.ReadBufferSize) if err := req.Write(netConn); err != nil { return nil, nil, err @@ -472,7 +469,7 @@ func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) u.WriteBufferSize = defaultWriteBufferSize } u.WriteBufferSize += maxFrameHeaderSize - if writeBuf == nil && u.WriteBufferPool == nil { + if u.WriteBufferPool == nil { writeBuf = make([]byte, u.WriteBufferSize) } }