diff --git a/plugins/wasm-go/extensions/rewrite/README.md b/plugins/wasm-go/extensions/rewrite/README.md new file mode 100644 index 0000000000..9c9c728868 --- /dev/null +++ b/plugins/wasm-go/extensions/rewrite/README.md @@ -0,0 +1,120 @@ +# 功能说明 + +`rewrite` 插件可以用于修改请求域名(Host)以及请求路径(Path),通常用于后端服务的域名/路由与网关侧域名/路由不一致时的配置,与 [Rewrite Annotation](https://higress.io/zh-cn/docs/user/annotation-use-case/#rewrite%E9%87%8D%E5%86%99path%E5%92%8Chost) 实现的效果一致。 + + +# 配置字段 + +| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | +|---------------|-----------------|------|-----|------------------------------| +| rewrite_rules | array of object | 选填 | - | 配置请求域名(Host)与请求路径(Path)的重写规则 | + +`rewrite_rules` 中配置字段说明如下: + +| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | +|-----------------|-----------------|------|-------|-------------------------------------------------------------------------------| +| match_path_type | string | 选填 | - | 配置请求路径的匹配类型,可选值为:前缀匹配 prefix, 精确匹配 exact, 正则匹配 regex | +| case_sensitive | bool | 选填 | false | 配置匹配时是否区分大小写,默认不区分 | +| match_hosts | array of string | 选填 | - | 配置会被重写的请求域名列表,支持精确匹配(hello.world.com),最左通配(\*.world.com)和最右通配(hello.world.\*) | +| match_paths | array of string | 选填 | - | 配置会被重写的请求路径列表,支持 [RE2](https://pkg.go.dev/regexp/syntax) 正则表达式语法 | +| rewrite_host | string | 选填 | - | 配置重写的目标域名 | +| rewrite_path | string | 选填 | - | 配置重写的目标路径 | + +**注意:** +- 只有当请求域名(Host)和请求路径(Path)都对应匹配到某条重写规则的 `match_hosts`、`match_paths` 中的一项时,才会将请求域名和请求路径分别重写为该条重写规则的 `rewrite_host`、`rewrite_path`; +- 当配置多条重写规则,将按照规则编写顺序进行匹配; +- 在一条重写规则中,`match_hosts` 和 `match_paths` 按照编写顺序进行匹配; +- `case_sensitive` 也会作用到正则表达式的匹配上。 + + +# 配置示例 + +## 前缀匹配请求路径 + +以下配置将请求路径的匹配类型设置为前缀匹配(prefix): + +```yaml +rewrite_rules: + - match_path_type: prefix # 前缀匹配 + case_sensitive: false + match_hosts: + - foo.bar.com + match_paths: + - /v1/api/get + rewrite_host: prefix.example.com + rewrite_path: /get +``` + +示例请求 `foo.bar.com/v1/api/get/something` 将被重写为 `prefix.example.com/get`。 + +## 正则匹配请求路径 + +以下配置将请求路径的匹配类型设置为正则匹配(regex): + +```yaml +rewrite_rules: + - match_path_type: regex # 正则匹配 + case_sensitive: false + match_hosts: + - aa.bb.cc + - foo.bar.com + match_paths: + - /abc/(get) + - /(get)/.*\.html + rewrite_host: regex.example.com + rewrite_path: /$1 +``` + +以下示例请求将被重写为 `regex.example.com/get`: +- `foo.bar.com/abc/get`; +- `aa.bb.cc/get/index.html`。 + + +## 通配请求域名 + +以下配置演示请求域名的精准匹配和最左、最右通配: + +```yaml +rewrite_rules: + - match_path_type: exact + case_sensitive: false + match_hosts: + - "hello.world.com" # 精准匹配 + - "*.example.com" # 最左通配 + - "aa.bb.*" # 最右通配 + match_paths: + - /v1/get + - /abc/get/ + rewrite_host: wildcard.example.com + rewrite_path: /get +``` + +以下示例请求将被重写为 `wildcard.example.com/get`: +- `hello.world.com/abc/get/`; +- `my.example.com/v1/get`; +- `aa.bb.com/v1/get`。 + +## 大小写敏感 + +以下配置将请求域名和请求路径的匹配设置为大小写敏感: + +```yaml +rewrite_rules: + - match_path_type: regex + case_sensitive: true # 大小写敏感 + match_hosts: + - xx.yy.ZZ + match_paths: + - /API/(get) + - /test/(get)/.*\.HTML + rewrite_host: case-sensitive.example.com + rewrite_path: /$1 +``` + +以下示例请求将被重写为 `case-sensitive.example.com/get`: +- `xx.yy.ZZ/API/get`; +- `xx.yy.ZZ/test/get/index.HTML`。 + +而以下示例请求则因为大小写敏感而无法正确匹配: +- `xx.yy.ZZ/api/get`; +- `xx.yy.zz/test/get/index.HTML`。 \ No newline at end of file diff --git a/plugins/wasm-go/extensions/rewrite/VERSION b/plugins/wasm-go/extensions/rewrite/VERSION new file mode 100644 index 0000000000..afaf360d37 --- /dev/null +++ b/plugins/wasm-go/extensions/rewrite/VERSION @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/plugins/wasm-go/extensions/rewrite/docker-compose.yaml b/plugins/wasm-go/extensions/rewrite/docker-compose.yaml new file mode 100644 index 0000000000..339e3b87e5 --- /dev/null +++ b/plugins/wasm-go/extensions/rewrite/docker-compose.yaml @@ -0,0 +1,24 @@ +version: '3.7' +services: + envoy: + image: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/envoy:1.20 + depends_on: + - httpbin + networks: + - wasmtest + ports: + - "10000:10000" + - "9901:9901" + volumes: + - ./envoy.yaml:/etc/envoy/envoy.yaml + - ./plugin.wasm:/etc/envoy/main.wasm + + httpbin: + image: kennethreitz/httpbin:latest + networks: + - wasmtest + ports: + - "12345:80" + +networks: + wasmtest: {} \ No newline at end of file diff --git a/plugins/wasm-go/extensions/rewrite/envoy.yaml b/plugins/wasm-go/extensions/rewrite/envoy.yaml new file mode 100644 index 0000000000..134f6d831f --- /dev/null +++ b/plugins/wasm-go/extensions/rewrite/envoy.yaml @@ -0,0 +1,101 @@ +admin: + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 9901 +static_resources: + listeners: + - name: listener_0 + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + scheme_header_transformation: + scheme_to_overwrite: https + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: + prefix: "/" + name: "foo" + route: + cluster: httpbin + http_filters: + - name: wasmdemo + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm + value: + config: + name: wasmdemo + vm_config: + runtime: envoy.wasm.runtime.v8 + code: + local: + filename: /etc/envoy/main.wasm + configuration: + "@type": "type.googleapis.com/google.protobuf.StringValue" + value: | + { + "rewrite_rules": [ + { + "match_path_type": "prefix", + "case_sensitive": false, + "match_hosts": ["foo.bar.com"], + "match_paths": ["/v1/api/get"], + "rewrite_host": "prefix.example.com", + "rewrite_path": "/get" + }, + { + "match_path_type": "regex", + "case_sensitive": false, + "match_hosts": ["aa.bb.cc", "foo.bar.com"], + "match_paths": ["/abc/(get)", "/(get)/.*\\.html"], + "rewrite_host": "regex.example.com", + "rewrite_path": "/$1" + }, + { + "match_path_type": "exact", + "case_sensitive": false, + "match_hosts": ["hello.world.com", "*.example.com", "aa.bb.*"], + "match_paths": ["/v1/get", "/abc/get/"], + "rewrite_host": "wildcard.example.com", + "rewrite_path": "/get" + }, + { + "match_path_type": "regex", + "case_sensitive": true, + "match_hosts": ["xx.yy.ZZ"], + "match_paths": ["/API/(get)", "/test/(get)/.*\\.HTML"], + "rewrite_host": "case-sensitive.example.com", + "rewrite_path": "/$1" + } + ] + } + - name: envoy.filters.http.router + clusters: + - name: httpbin + connect_timeout: 30s + type: LOGICAL_DNS + dns_lookup_family: V4_ONLY + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: httpbin + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: httpbin + port_value: 80 \ No newline at end of file diff --git a/plugins/wasm-go/extensions/rewrite/go.mod b/plugins/wasm-go/extensions/rewrite/go.mod new file mode 100644 index 0000000000..a296dec859 --- /dev/null +++ b/plugins/wasm-go/extensions/rewrite/go.mod @@ -0,0 +1,16 @@ +module github.com/alibaba/higress/plugins/wasm-go/extensions/rewrite + +go 1.19 + +require ( + github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230512062358-6b483189acb9 + github.com/dlclark/regexp2 v1.10.0 + github.com/tetratelabs/proxy-wasm-go-sdk v0.19.1-0.20220822060051-f9d179a57f8c + github.com/tidwall/gjson v1.14.3 +) + +require ( + github.com/google/uuid v1.3.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect +) diff --git a/plugins/wasm-go/extensions/rewrite/go.sum b/plugins/wasm-go/extensions/rewrite/go.sum new file mode 100644 index 0000000000..d08acace2b --- /dev/null +++ b/plugins/wasm-go/extensions/rewrite/go.sum @@ -0,0 +1,18 @@ +github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230512062358-6b483189acb9 h1:j5Q2jO2EB3wPJeyUbpcRMAQb+InH+VeARNpt9lNgH7M= +github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230512062358-6b483189acb9/go.mod h1:AzSnkuon5c26nIePTiJQIAFsKdhkNdncLcTuahpGtQs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= +github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/tetratelabs/proxy-wasm-go-sdk v0.19.1-0.20220822060051-f9d179a57f8c h1:OCUFXVGixHLfNjg6/QYEhv+jHJ5mRGhpEUVFv9eWPJE= +github.com/tetratelabs/proxy-wasm-go-sdk v0.19.1-0.20220822060051-f9d179a57f8c/go.mod h1:5t/pWFNJ9eMyu/K/Z+OeGhDJ9sN9eCo8fc2pyM/Qjg4= +github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= +github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/plugins/wasm-go/extensions/rewrite/main.go b/plugins/wasm-go/extensions/rewrite/main.go new file mode 100644 index 0000000000..cc88c76cef --- /dev/null +++ b/plugins/wasm-go/extensions/rewrite/main.go @@ -0,0 +1,171 @@ +// Copyright (c) 2022 Alibaba Group Holding Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "strings" + + "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm" + "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" + "github.com/tidwall/gjson" + + "github.com/alibaba/higress/plugins/wasm-go/extensions/rewrite/rewriter" + "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" +) + +func main() { + wrapper.SetCtx( + "rewrite", + wrapper.ParseConfigBy(parseConfig), + wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders), + ) +} + +type RewriteConfig struct { + rewriteRules []RewriteRule +} + +type RewriteRule struct { + matchPathType string // prefix | exact | regex + caseSensitive bool + matchHosts []string + matchPaths []string + rewriteHost string + rewritePath string +} + +var rewriters []*rewriter.Rewriter + +func parseConfig(json gjson.Result, config *RewriteConfig, log wrapper.Log) error { + log.Debug("[parseConfig]") + + var path, host string + rules := json.Get("rewrite_rules").Array() + config.rewriteRules = make([]RewriteRule, 0, len(rules)) + rewriters = make([]*rewriter.Rewriter, 0, len(rules)) + for _, rule := range rules { + var rr RewriteRule + rr.matchPathType = rule.Get("match_path_type").String() + rr.caseSensitive = rule.Get("case_sensitive").Bool() + rr.rewriteHost = rule.Get("rewrite_host").String() + rr.rewritePath = rule.Get("rewrite_path").String() + for _, item := range rule.Get("match_hosts").Array() { + if host = item.String(); host == "" { + continue + } + if rr.caseSensitive { + rr.matchHosts = append(rr.matchHosts, host) + } else { + rr.matchHosts = append(rr.matchHosts, strings.ToLower(host)) + } + } + for _, item := range rule.Get("match_paths").Array() { + if path = item.String(); path == "" { + continue + } + if rr.caseSensitive { + rr.matchPaths = append(rr.matchPaths, path) + } else { + rr.matchPaths = append(rr.matchPaths, strings.ToLower(path)) + } + } + config.rewriteRules = append(config.rewriteRules, rr) + + // config -> rewriters + // rules[i] <-> rewriters[i] + rw := rewriter.NewRewriter(len(rr.matchHosts), len(rr.matchPaths), rr.rewriteHost, rr.rewritePath) + for _, matchHost := range rr.matchHosts { + matchHostType := rewriter.HostUnknown + if strings.HasSuffix(matchHost, "*") { + matchHostType = rewriter.HostPrefix + matchHost = matchHost[:len(matchHost)-1] + } else if strings.HasPrefix(matchHost, "*") { + matchHostType = rewriter.HostSuffix + matchHost = matchHost[1:] + } else { + matchHostType = rewriter.HostExact + } + rw.AppendHostMatcher(matchHostType, matchHost) + } + + matchPathType := rewriter.PathUnknown + switch rr.matchPathType { + case "prefix": + matchPathType = rewriter.PathPrefix + case "exact": + matchPathType = rewriter.PathExact + case "regex": + matchPathType = rewriter.PathRegex + } + for _, matchPath := range rr.matchPaths { + rw.AppendPathMatcher(matchPathType, matchPath, matchPath) + } + + rewriters = append(rewriters, rw) + } + + log.Debugf("rewrite config: %+v", *config) + + return nil +} + +func onHttpRequestHeaders(ctx wrapper.HttpContext, config RewriteConfig, log wrapper.Log) types.Action { + log.Debug("[onHttpRequestHeaders]") + + var ( + host, path = ctx.Host(), ctx.Path() // keep the original + reqHost, reqPath string + matched bool + rewriteHost, rewritePath string + ) + log.Debugf("request: host=%s, path=%s", host, path) + + for i, rule := range config.rewriteRules { + reqHost, reqPath = host, path + if !rule.caseSensitive { + reqHost = strings.ToLower(host) + reqPath = strings.ToLower(path) + } + matched, rewriteHost, rewritePath = rewriters[i].MatchAndRewrite(reqHost, reqPath) + if matched { + log.Debugf("match rule#%d, type: %s, rewrite: host[%s -> %s] path[%s -> %s]", i, rule.matchPathType, reqHost, rewriteHost, reqPath, rewritePath) + break + } + } + if !matched { + log.Debug("unmatched") + return types.ActionContinue + } + + hs, err := proxywasm.GetHttpRequestHeaders() + if err != nil { + log.Warnf("get request headers failed: %v", err) + } + for i, h := range hs { + if h[0] == ":authority" { + hs[i][1] = rewriteHost + } else if h[0] == ":path" { + hs[i][1] = rewritePath + } + } + err = proxywasm.ReplaceHttpRequestHeaders(hs) + if err != nil { + log.Warnf("replace request headers failed: %v", err) + return types.ActionContinue + } + log.Debug("rewrite successful") + + return types.ActionContinue +} diff --git a/plugins/wasm-go/extensions/rewrite/rewrite.yaml b/plugins/wasm-go/extensions/rewrite/rewrite.yaml new file mode 100644 index 0000000000..ebd9ce4e9d --- /dev/null +++ b/plugins/wasm-go/extensions/rewrite/rewrite.yaml @@ -0,0 +1,61 @@ +apiVersion: extensions.higress.io/v1alpha1 +kind: WasmPlugin +metadata: + annotations: + higress.io/wasm-plugin-description: 用于修改请求域名(Host)以及请求路径(Path),通常用于后端服务的域名/路由与网关侧域名/路由不一致时的配置 + higress.io/wasm-plugin-icon: https://img.alicdn.com/imgextra/i1/O1CN018iKKih1iVx287RltL_!!6000000004419-2-tps-42-42.png + higress.io/wasm-plugin-title: Rewrite + labels: + higress.io/resource-definer: higress + higress.io/wasm-plugin-built-in: "false" + higress.io/wasm-plugin-category: custom + higress.io/wasm-plugin-name: rewrite + higress.io/wasm-plugin-version: 1.0.0 + name: rewrite-1.0.0 + namespace: higress-system +spec: + defaultConfig: + rewrite_rules: + - match_path_type: prefix + case_sensitive: false + match_hosts: + - foo.bar.com + match_paths: + - /v1/api/get + rewrite_host: prefix.example.com + rewrite_path: /get + - match_path_type: regex + case_sensitive: false + match_hosts: + - aa.bb.cc + - foo.bar.com + match_paths: + - /abc/(get) + - /(get)/.*\.html + rewrite_host: regex.example.com + rewrite_path: /$1 + - match_path_type: exact + case_sensitive: false + match_hosts: + - "hello.world.com" + - "*.example.com" + - "aa.bb.*" + match_paths: + - /v1/get + - /abc/get/ + rewrite_host: wildcard.example.com + rewrite_path: /get + - match_path_type: regex + case_sensitive: true + match_hosts: + - xx.yy.ZZ + match_paths: + - /API/(get) + - /test/(get)/.*\.HTML + rewrite_host: case-sensitive.example.com + rewrite_path: /$1 + defaultConfigDisable: false + + phase: UNSPECIFIED_PHASE + priority: 20 + url: oci://docker.io/weixinx/rewrite:1.0.0 \ No newline at end of file diff --git a/plugins/wasm-go/extensions/rewrite/rewriter/rewriter.go b/plugins/wasm-go/extensions/rewrite/rewriter/rewriter.go new file mode 100644 index 0000000000..27d34f7845 --- /dev/null +++ b/plugins/wasm-go/extensions/rewrite/rewriter/rewriter.go @@ -0,0 +1,145 @@ +// Copyright (c) 2022 Alibaba Group Holding Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rewriter + +import ( + "regexp" + "strings" +) + +type Rewriter struct { + hostMatchers []*HostMatcher + pathMatchers []*PathMatcher + rewriteHost string + rewritePath string +} + +type HostMatcher struct { + matchType MatchHostType + host string +} + +type PathMatcher struct { + matchType MatchPathType + path string + reg *regexp.Regexp +} + +type MatchHostType int + +const ( + HostPrefix MatchHostType = iota + HostSuffix + HostExact + HostUnknown +) + +type MatchPathType int + +const ( + PathPrefix MatchPathType = iota + PathExact + PathRegex + PathUnknown +) + +func NewRewriter(hostNum, pathNum int, rewriteHost, rewritePath string) *Rewriter { + return &Rewriter{ + hostMatchers: make([]*HostMatcher, 0, hostNum), + pathMatchers: make([]*PathMatcher, 0, pathNum), + rewriteHost: rewriteHost, + rewritePath: rewritePath, + } +} + +func (r *Rewriter) AppendHostMatcher(matchType MatchHostType, host string) { + r.hostMatchers = append(r.hostMatchers, &HostMatcher{ + matchType: matchType, + host: host, + }) +} + +func (r *Rewriter) AppendPathMatcher(matchType MatchPathType, path, pattern string) { + r.pathMatchers = append(r.pathMatchers, &PathMatcher{ + matchType: matchType, + path: path, + reg: regexp.MustCompile(pattern), + }) +} + +func (r Rewriter) MatchAndRewrite(reqHost, reqPath string) (matched bool, rewriteHost, rewritePath string) { + var hostMatched, pathMatched bool + for _, hm := range r.hostMatchers { + if hm.match(reqHost) { + hostMatched = true + rewriteHost = r.rewriteHost + break + } + } + if !hostMatched { + return + } + + for _, pm := range r.pathMatchers { + if pm.match(reqPath) { + pathMatched = true + if pm.matchType == PathRegex { + // e.g. + // if: + // regexPattern = "/v1/(app)" + // reqPath = "/v1/app" + // r.rewritePath = "/$1" + // then: + // rewritePath = "/app" + rewritePath = pm.reg.ReplaceAllString(reqPath, r.rewritePath) + } else { + rewritePath = r.rewritePath + } + break + } + } + if pathMatched { + matched = true + return + } + + return +} + +func (hm HostMatcher) match(reqHost string) bool { + switch hm.matchType { + case HostPrefix: + return strings.HasPrefix(reqHost, hm.host) + case HostSuffix: + return strings.HasSuffix(reqHost, hm.host) + case HostExact: + return reqHost == hm.host + } + return false +} + +func (pm PathMatcher) match(reqPath string) bool { + switch pm.matchType { + case PathPrefix: + return strings.HasPrefix(reqPath, pm.path) + case PathExact: + return reqPath == pm.path + case PathRegex: + if ok := pm.reg.MatchString(reqPath); ok { + return true + } + } + return false +}