diff --git a/CHANGELOG.txt b/CHANGELOG.txt index d4fb45f..54f87df 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,8 @@ +v1.0.4: + 1. add js_lua middleware. + + 1. 添加js_lua 中间件。 + v1.0.3: 1. add middleware sub_filter. diff --git a/FEATURES.txt b/FEATURES.txt index 8157e5f..3b1b37c 100644 --- a/FEATURES.txt +++ b/FEATURES.txt @@ -1,5 +1,8 @@ EN: - 1. add middleware sub_filter. + 1. add js_lua middleware. ZH: - 1. 添加中间件sub_filter。 + 1. 添加js_lua 中间件。 + + + diff --git a/cc/cfg.go b/cc/cfg.go index 8f53738..dfb3ebe 100644 --- a/cc/cfg.go +++ b/cc/cfg.go @@ -11,7 +11,7 @@ import ( "github.com/BurntSushi/toml" ) -var C Cfg +var Config Cfg // proxyUrl add proxy Config to help DefaultTransport send the request to // urls specified in the configuration @@ -56,6 +56,10 @@ type Cfg struct { // sub_filter SubFilters map[string]SubFilter `toml:"SubFilter"` + + // js lua scripts + LuaPath string `toml:"LuaPath"` + JsPath string `toml:"JSPath"` } // InitConfig pass in a filename and reread all config from file to cover origin value @@ -65,6 +69,9 @@ func (c *Cfg) InitConfig(filename string) error { if _, err = toml.DecodeFile(filename, c); err != nil { return err } + c.SetLogFlag() + // print config after flag was set + c.Print() if err = c.parseProxyUrls(); err != nil { return err } diff --git a/cc/magic.go b/cc/magic.go new file mode 100644 index 0000000..c30572a --- /dev/null +++ b/cc/magic.go @@ -0,0 +1,3 @@ +package cc + +const UNIQUEID = "unique_id" diff --git a/cmd/main.go b/cmd/main.go index 1ae73cd..c7708d7 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -18,7 +18,7 @@ import ( "superlcx/core" ) -const version = "1.0.3" +const version = "1.0.4" var ( showVersion bool @@ -29,18 +29,19 @@ func init() { defer func() { err := recover() if err != nil { + fmt.Print(err) flag.PrintDefaults() os.Exit(-1) } }() flag.BoolVar(&showVersion, "v", false, "show version and about then exit.") flag.StringVar(&configFile, "c", "", "load config from") - flag.IntVar(&C.ListenPort, "l", 8080, "listen port") - flag.IntVar(&C.PPROFPort, "pp", 8999, "pprof port") - flag.StringVar(&C.DefaultTarget, "host", "0.0.0.0:8081", "target host:port.") - flag.StringVar(&C.Mode, "m", "proxy", "run mode .") - flag.StringVar(&C.Middleware, "M", "", "middleware, comma separated if more than one, eg: --M stdout,dumps") - flag.StringVar(&C.LogFlag, "log", "t", "l -> line of code, d -> date, t -> time, order doesn't matter") + flag.IntVar(&Config.ListenPort, "l", 8080, "listen port") + flag.IntVar(&Config.PPROFPort, "pp", 8999, "pprof port") + flag.StringVar(&Config.DefaultTarget, "host", "0.0.0.0:8081", "target host:port.") + flag.StringVar(&Config.Mode, "m", "proxy", "run mode .") + flag.StringVar(&Config.Middleware, "M", "", "middleware, comma separated if more than one, eg: --M stdout,dumps") + flag.StringVar(&Config.LogFlag, "log", "t", "l -> line of code, d -> date, t -> time, order doesn't matter") flag.Parse() if showVersion { fmt.Printf(` @@ -55,44 +56,42 @@ Superlcx [%s], a tool kit for port transfer with middlewares! `, version) os.Exit(0) } + if configFile != "" { - err := C.InitConfig(configFile) + err := Config.InitConfig(configFile) if err != nil { panic(err) } } - C.SetLogFlag() - // print config after flag was set - C.Print() - if C.ListenPort < 1 || C.ListenPort > 65535 { + if Config.ListenPort < 1 || Config.ListenPort > 65535 { panic("[x] Listen Port Invalid") } - checkHost(C.DefaultTarget) + checkHost(Config.DefaultTarget) } func main() { // Buried point for debug go func() { - http.ListenAndServe(fmt.Sprintf(":%d", C.PPROFPort), nil) + http.ListenAndServe(fmt.Sprintf(":%d", Config.PPROFPort), nil) }() go showMemLog() // start listen - lis, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", C.ListenPort)) + lis, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", Config.ListenPort)) if err != nil { panic(err) } - switch C.Mode { + switch Config.Mode { case "proxy": - c := core.NewSapProxy(lis, C) + c := core.NewSapProxy(lis, Config) c.Serve() case "copy": - c := core.NewSapCopy(lis, C) + c := core.NewSapCopy(lis, Config) c.Serve() case "blend": - c := core.NewSapBlend(lis, C) + c := core.NewSapBlend(lis, Config) c.Serve() default: flag.PrintDefaults() diff --git a/config.toml b/config.toml index df7531f..76e4328 100644 --- a/config.toml +++ b/config.toml @@ -2,8 +2,10 @@ ListenPort = 8081 DefaultTarget = "0.0.0.0:8080" PPROFPort = 8999 LogFlag = "ltd" -Middleware = "sub_filter,c_header,stdout" +Middleware = "sub_filter,c_header,stdout,js_lua" Mode = "blend" +LuaPath="./middlewares/js_lua/sub_custom.lua" +JsPath="./middlewares/js_lua/sub_custom.js" # pass_proxy work on blend and proxy mode [ProxyUrls] diff --git a/core/common.go b/core/common.go index 36e76e4..c09bcda 100644 --- a/core/common.go +++ b/core/common.go @@ -6,8 +6,11 @@ import ( "net/url" "strings" + "github.com/google/uuid" + "superlcx/cc" "superlcx/middlewares/c_header" + "superlcx/middlewares/js_lua" "superlcx/middlewares/stdout" "superlcx/middlewares/sub_filter" ) @@ -29,8 +32,8 @@ func organizeUrl(req *http.Request, defaultT *url.URL) { return a + b } var target *url.URL = nil - if cc.C.ProxyUrls != nil && len(cc.C.ProxyUrls) > 0 { - for _, proxyUrl := range cc.C.ProxyUrls { + if cc.Config.ProxyUrls != nil && len(cc.Config.ProxyUrls) > 0 { + for _, proxyUrl := range cc.Config.ProxyUrls { if proxyUrl.Re.MatchString(req.URL.RequestURI()) { target = proxyUrl.U break @@ -50,6 +53,8 @@ func organizeUrl(req *http.Request, defaultT *url.URL) { } else { req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery } + + req.Header.Add(cc.UNIQUEID, uuid.New().String()) } type middleware struct { @@ -73,6 +78,8 @@ func newMiddleware(mid string) *middleware { middle.RegisterMiddleware(c_header.HandleRequest, c_header.HandleResponse) case "sub_filter": middle.RegisterMiddleware(sub_filter.HandleRequest, sub_filter.HandleResponse) + case "js_lua": + middle.RegisterMiddleware(js_lua.HandleRequest, js_lua.HandleResponse) default: reqH, respH := find(m) middle.RegisterMiddleware(reqH, respH) diff --git a/docs/middleware.CN.md b/docs/middleware.CN.md index 974652e..b944965 100644 --- a/docs/middleware.CN.md +++ b/docs/middleware.CN.md @@ -60,3 +60,19 @@ newReq, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(content))) Repl='' # 替换为 Path="/" # 指定url(支持正则表达式) ``` + + +### js_lua +使用解释型语言来保守的扩展功能,是作为频繁更新的另一选择。 +该程序采用了如下两个模块库,来实现lua和js的调用 +``` +"github.com/robertkrimen/otto" +"github.com/yuin/gopher-lua" +``` +配置如下 +```toml +Middleware = "js_lua" +LuaPath="./middlewares/js_lua/sub_custom.lua" +JsPath="./middlewares/js_lua/sub_custom.js" +``` +只有在middleware中添加了该脚本,并且在LuaPath和JsPath不为空时,才会载入脚本。 \ No newline at end of file diff --git a/docs/middleware.md b/docs/middleware.md index 33781d6..9ddc3fa 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -63,3 +63,21 @@ The middleware is the same as the sub_filter function in NGINX. For the specifie Repl='' # what will be replace if matched Path="/" # specific url(Support for regular expressions) ``` + +### js_lua +Using interpreted languages to conservatively extend functionality is another alternative for frequent updates. +The program USES the following two libraries to implement invoke of Lua and Js. +``` +"github.com/robertkrimen/otto" +"github.com/yuin/gopher-lua" +``` + +Configuration is as follows + +```toml +Middleware = "js_lua" +LuaPath="./middlewares/js_lua/sub_custom.lua" +JsPath="./middlewares/js_lua/sub_custom.js" +``` + +This two scripts will be added as middleware only when LuaPath and JsPath are not empty and middleware js_lua was added. \ No newline at end of file diff --git a/go.mod b/go.mod index 3ec5feb..fe71722 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,10 @@ module superlcx go 1.14 -require github.com/BurntSushi/toml v0.3.1 +require ( + github.com/BurntSushi/toml v0.3.1 + github.com/google/uuid v1.1.1 + github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff + github.com/yuin/gopher-lua v0.0.0-20200603152657-dc2b0ca8b37e + gopkg.in/sourcemap.v1 v1.0.5 // indirect +) diff --git a/go.sum b/go.sum index 9cb2df8..7fdc656 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,14 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff h1:+6NUiITWwE5q1KO6SAfUX918c+Tab0+tGAM/mtdlUyA= +github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY= +github.com/yuin/gopher-lua v0.0.0-20200603152657-dc2b0ca8b37e h1:oIpIX9VKxSCFrfjsKpluGbNPBGq9iNnT9crH781j9wY= +github.com/yuin/gopher-lua v0.0.0-20200603152657-dc2b0ca8b37e/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= +gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= diff --git a/middlewares/c_header/handler.go b/middlewares/c_header/handler.go index 565f00c..17601c4 100644 --- a/middlewares/c_header/handler.go +++ b/middlewares/c_header/handler.go @@ -8,10 +8,10 @@ import ( ) func HandleRequest(req *http.Request) { - if C.CustomHeaders == nil || len(C.CustomHeaders) == 0 { + if Config.CustomHeaders == nil || len(Config.CustomHeaders) == 0 { return } - for k, v := range C.CustomHeaders { + for k, v := range Config.CustomHeaders { if strings.HasPrefix(k, "req") { // log.Printf("add header kv to req k:[%s],v:[%s]", v.Key, v.Value) req.Header.Set(v.Key, v.Value) @@ -20,10 +20,10 @@ func HandleRequest(req *http.Request) { } func HandleResponse(resp *http.Response) { - if C.CustomHeaders == nil || len(C.CustomHeaders) == 0 { + if Config.CustomHeaders == nil || len(Config.CustomHeaders) == 0 { return } - for k, v := range C.CustomHeaders { + for k, v := range Config.CustomHeaders { if strings.HasPrefix(k, "resp") { // log.Printf("add header kv to resp k:[%s],v:[%s]", v.Key, v.Value) resp.Header.Set(v.Key, v.Value) diff --git a/middlewares/js_lua/handler.go b/middlewares/js_lua/handler.go new file mode 100644 index 0000000..e00eec0 --- /dev/null +++ b/middlewares/js_lua/handler.go @@ -0,0 +1,253 @@ +package js_lua + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/http/httputil" + "strconv" + "strings" + "sync" + "time" + + "github.com/robertkrimen/otto" + lua "github.com/yuin/gopher-lua" + + "superlcx/cc" +) + +var Vm = VM{Lua: nil, Js: nil, + LMap: sync.Map{}, JMap: sync.Map{}, +} + +func init() { + + go func() { + time.Sleep(1 * time.Second) + if cc.Config.JsPath != "" && strings.Contains(cc.Config.Middleware, "js_lua") { + log.Printf("load js vm from file %s", cc.Config.JsPath) + Vm.J.Lock() + defer Vm.J.Unlock() + jscode, err := ioutil.ReadFile(cc.Config.JsPath) + if err != nil { + panic(err) + } + Vm.Js = otto.New() + _, err = Vm.Js.Run(jscode) + if err != nil { + panic(err) + } + } + + if cc.Config.LuaPath != "" && strings.Contains(cc.Config.Middleware, "js_lua") { + log.Printf("load lua vm from file %s", cc.Config.LuaPath) + Vm.L.Lock() + defer Vm.L.Unlock() + Vm.Lua = lua.NewState() + Vm.Lua.PreloadModule("utils", SkyUtils) + err := Vm.Lua.DoFile(cc.Config.LuaPath) + if err != nil { + panic(err) + } + } + }() +} + +var emptyBody = []byte("") + +func HandleRequest(req *http.Request) { + over := make(chan bool) + go handleRequest(over, req) + <-over +} + +func makeHeader(h http.Header) string { + var buf strings.Builder + for k, d := range h { + buf.WriteString(fmt.Sprintf("%s: %s\r\n", k, strings.Join(d, ";"))) + } + return buf.String() +} + +func handleRequest(over chan bool, req *http.Request) { + content, err := httputil.DumpRequest(req, true) + over <- true + if err != nil { + panic(err) + } + r := bufio.NewReader(bytes.NewReader(content)) + newReq, err := http.ReadRequest(r) + if err != nil { + panic(err) + } + bodyLen := newReq.ContentLength + body := make([]byte, bodyLen) + n, err := r.Read(body) + if err != nil || n <= 0 || len(body) > (1<<20) { + body = emptyBody[:] + } + uniqueId := newReq.Header.Get(cc.UNIQUEID) + newReq.Body.Close() + header := makeHeader(req.Header) + reqDic := map[string]string{ + cc.UNIQUEID: uniqueId, + "method": newReq.Method, + "url": newReq.URL.RequestURI(), + "proto": newReq.Proto, + "header": header, + "body": string(body), + } + var waitG sync.WaitGroup + if Vm.Js != nil { + go func(wg *sync.WaitGroup) { + wg.Add(1) + defer wg.Done() + + Vm.J.Lock() + defer Vm.J.Unlock() + reqJsa, err := Vm.Js.ToValue(reqDic) + if err != nil { + panic(err) + } + result, _ := Vm.Js.Call("on_http_request", nil, reqJsa) + v, _ := result.Export() + if res, ok := v.(bool); ok && res { + Vm.JMap.Store(uniqueId, res) + } + }(&waitG) + } + + if Vm.Lua != nil { + go func(wg *sync.WaitGroup) { + wg.Add(1) + defer wg.Done() + Vm.L.Lock() + defer Vm.L.Unlock() + reqTable := Vm.Lua.NewTable() + for k, v := range reqDic { + Vm.Lua.SetTable(reqTable, lua.LString(k), lua.LString(v)) + } + if err := Vm.Lua.CallByParam(lua.P{ + Fn: Vm.Lua.GetGlobal("on_http_request"), + NRet: 1, + Protect: true, + }, reqTable); err != nil { + panic(err) + } + ret := Vm.Lua.Get(-1) + Vm.Lua.Pop(1) + if ret.String() == "true" { + Vm.JMap.Store(uniqueId, true) + } + }(&waitG) + } + waitG.Wait() +} + +func HandleResponse(resp *http.Response) { + over := make(chan bool) + go handlerResponse(over, resp) + <-over +} + +func handlerResponse(over chan bool, resp *http.Response) { + var buf bytes.Buffer + n, err := buf.ReadFrom(resp.Body) + if err != nil { + log.Printf("[x] read body error, detail %s", err) + } + err = resp.Body.Close() + if err != nil { + log.Printf("[x] close body error, detail %s", err) + } + resp.ContentLength = n + resp.Header.Set("Content-Length", strconv.Itoa(int(n))) + resp.Body = ioutil.NopCloser(&buf) + over <- true + uniqueId := resp.Request.Header.Get(cc.UNIQUEID) + body := buf.Bytes() + header := makeHeader(resp.Header) + respDic := map[string]string{ + cc.UNIQUEID: uniqueId, + "status": fmt.Sprintf("%d", resp.StatusCode), + "header": header, + "body": string(body), + } + + var waitG sync.WaitGroup + if Vm.Js != nil { + go func(wg *sync.WaitGroup) { + wg.Add(1) + defer wg.Done() + + Vm.J.Lock() + defer Vm.J.Unlock() + respJsa, err := Vm.Js.ToValue(respDic) + if err != nil { + panic(err) + } + _, _ = Vm.Js.Call("on_http_response", nil, respJsa) + }(&waitG) + } + + if Vm.Lua != nil { + go func(wg *sync.WaitGroup) { + wg.Add(1) + defer wg.Done() + Vm.L.Lock() + defer Vm.L.Unlock() + respTable := Vm.Lua.NewTable() + for k, v := range respDic { + Vm.Lua.SetTable(respTable, lua.LString(k), lua.LString(v)) + } + if err := Vm.Lua.CallByParam(lua.P{ + Fn: Vm.Lua.GetGlobal("on_http_response"), + NRet: 0, + Protect: true, + }, respTable); err != nil { + panic(err) + } + }(&waitG) + } + waitG.Wait() +} + +type VM struct { + Lua *lua.LState + L sync.Mutex + LMap sync.Map + + Js *otto.Otto + J sync.Mutex + JMap sync.Map +} + +func SkyUtils(L *lua.LState) int { + mod := L.SetFuncs(L.NewTable(), exports) + L.Push(mod) + return 1 +} + +var exports = map[string]lua.LGFunction{ + "decode": decode, +} + +func decode(L *lua.LState) int { + r := L.Get(-1) + L.Pop(1) + btable := make(map[string]interface{}) + err := json.Unmarshal([]byte(r.String()), &btable) + if err != nil { + panic(err) + } + table := L.NewTable() + for k, v := range btable { + L.SetTable(table, lua.LString(k), lua.LString(fmt.Sprintf("%s", v))) + } + L.Push(table) + return 1 +} diff --git a/middlewares/js_lua/sub_custom.js b/middlewares/js_lua/sub_custom.js new file mode 100644 index 0000000..e0ba12e --- /dev/null +++ b/middlewares/js_lua/sub_custom.js @@ -0,0 +1,35 @@ +var global_login_collection = {}; + +var create_login_obj = function (username, password) { + return {"username": username, "password": password}; +}; + +var on_http_request = function (req_tab) { + console.log("[js] invoke js on_http_request") + if (req_tab["method"] !== "POST" || req_tab["url"].indexOf("login") === -1) { + return false; + } + if (req_tab["body"] === "") { + return false; + } + var body = JSON.parse(req_tab["body"]); + if (body["username"] === "" || body["password"] === "") { + return false; + } + console.log("[js] login with username:", body["username"], "password: ", body["password"]) + global_login_collection[req_tab["unique_id"]] = create_login_obj(body["username"], body["password"]); + return true; +}; + +var on_http_response = function (resp_tab) { + console.log("[js] invoke js on_http_response") + if (!global_login_collection.hasOwnProperty(resp_tab["unique_id"])) { + return; + } + var obj = global_login_collection[resp_tab["unique_id"]]; + + delete global_login_collection[resp_tab["unique_id"]]; + + console.log("[js] delete key unique_id", resp_tab["unique_id"]) +}; + diff --git a/middlewares/js_lua/sub_custom.lua b/middlewares/js_lua/sub_custom.lua new file mode 100644 index 0000000..3574979 --- /dev/null +++ b/middlewares/js_lua/sub_custom.lua @@ -0,0 +1,71 @@ +local utils = require("utils") + +global_login_collection = {} + +function table.kIn(tbl, key) + if tbl == nil then + return false + end + for k, v in pairs(tbl) do + if k == key then + return true + end + end + return false +end + +function table.removeKey(tab, val) + for i, v in ipairs (tab) do + if (v.id == val) then + tab[i] = nil + end + end +end + +function on_http_request(req_tab) + print("[lua] invoke lua on_http_request") + if (not req_tab["method"] == "POST" or string.find(req_tab["url"],"login") == nil) + then + return false + end + if (req_tab["body"] == "" or req_tab["body"] == nil) + then + return false + end + + local res = utils.decode(req_tab["body"]) + + if (res["username"] == "" or res["password"] == "") + then + return false + end + print(string.format("[lua] login with username %s, password %s", res["username"], res["password"])) + global_login_collection[req_tab["unique_id"]] = res + return true +end + +function on_http_response(resp_tab) + print("[lua] invoke lua on_http_response") + if not table.kIn(global_login_collection, resp_tab["unique_id"]) + then + return + end + + local obj = global_login_collection[resp_tab["unique_id"]]; + + if (resp_tab["body"] == "" or resp_tab["body"] == nil) + then + return + end + local body = utils.decode(resp_tab["body"]) + + local risk_level = 'medium' + local login_success = false + if (body["code"] == 0 or body["code"] == "0") + then + login_success = true + risk_level = 'high' + end + table.removeKey(global_login_collection, resp_tab["unique_id"]) + print(string.format("[lua] delete key unique_id %s",resp_tab["unique_id"])) +end diff --git a/middlewares/sub_filter/handler.go b/middlewares/sub_filter/handler.go index 6d51875..80e91b0 100644 --- a/middlewares/sub_filter/handler.go +++ b/middlewares/sub_filter/handler.go @@ -17,15 +17,15 @@ func HandleRequest(req *http.Request) { } func HandleResponse(resp *http.Response) { - if C.SubFilters == nil || len(C.SubFilters) == 0 { + if Config.SubFilters == nil || len(Config.SubFilters) == 0 { return } ruri := resp.Request.URL.RequestURI() log.Printf("check invoke subFilter on url [%s]", ruri) var mk []SubFilter - if C.SubFilters != nil && len(C.SubFilters) != 0 { - for sub := range C.SubFilters { - f := C.SubFilters[sub] + if Config.SubFilters != nil && len(Config.SubFilters) != 0 { + for sub := range Config.SubFilters { + f := Config.SubFilters[sub] if f.RUriMatcher.MatchString(ruri) { mk = append(mk, f) }