]> 127.0.0.1 Git - front/.git/commitdiff
1 v0.1.20240321070814
authorqydysky <qydysky@foxmail.com>
Thu, 21 Mar 2024 07:05:01 +0000 (15:05 +0800)
committerqydysky <qydysky@foxmail.com>
Thu, 21 Mar 2024 07:05:01 +0000 (15:05 +0800)
13 files changed:
README.md
config.go
dealer/dealer.go [new file with mode: 0644]
dealer/header.go [new file with mode: 0644]
filiter/body.go [new file with mode: 0644]
filiter/filiter.go [new file with mode: 0644]
filiter/header.go [new file with mode: 0644]
filiter/uri.go [new file with mode: 0644]
go.mod
go.sum
http.go
main.go
ws.go

index 1ba4e2f30a6fe419d5d52619f9134a7036981b2b..56164a37fe4ce0e3161be2fe6b409d99e9dbdd60 100755 (executable)
--- 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`时将覆盖值。
index fde08db64985721a57f4c767b0108d564a221d72..2df1001a3451f92e7d41a228a029d52740bbe28c 100755 (executable)
--- 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 (file)
index 0000000..e6e02a7
--- /dev/null
@@ -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 (file)
index 0000000..98bd0af
--- /dev/null
@@ -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 (file)
index 0000000..d79c76b
--- /dev/null
@@ -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 (file)
index 0000000..b18d8a2
--- /dev/null
@@ -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 (file)
index 0000000..c80411e
--- /dev/null
@@ -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 (file)
index 0000000..3bc57a7
--- /dev/null
@@ -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 1479a0c772c96601e59f5b16f743c6de603f9016..222efe83ebec823164a333dd5353e48c61543a28 100755 (executable)
--- 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 f649799dfe8eadfbd217c0a4188dc6b5a112de9b..fc7659b967c165c8078d437e35bf73b6680390fa 100755 (executable)
--- 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 e49f0c08e43777baebf4005e7c697570f4edb01e..d3b6d27f2a0798df46a7a0d350db4657659e3570 100644 (file)
--- 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 25c071058a152352143ce5a5024dc2ad1875ed8c..cacde3dfdb31618414657defa0e5df63af239043 100755 (executable)
--- 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 3dd9900165d9a09f2c85f96349f676ef7a251066..39d1d852fd50e5641b479438cbfb13b0fc74991c 100644 (file)
--- 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)
                }
        }