diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 1587a0b94..012d7c12d 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -16,6 +16,7 @@ | Hao Dong | anotherwriter | | Haobin zhang | zhanghaobin | | Hui Yu | dblate | +| Gen Wang | gracewang510 | | Jie Liu | freeHackOfJeff | | Jie Wan | wanjiecs | | Jin Tong | cumirror | @@ -23,6 +24,7 @@ | Kaiyu Zheng | kaiyuzheng | | Lidong Chang | changlidong68 | | Lihua Chen | clh651188968 | +| Liujia Wei | weiliujia | | Limei Xiao | limeix | | Lei Zhang | deancn | | Lu Guo | guolu60 | @@ -41,6 +43,7 @@ | Shuai Yan | yanshuai615270 | | Sijie Yang | iyangsj | | Tianqi Zhang | NKztq | +| Weijie Zhao | zwj13513118235 | | Weiqiang Zheng | wrayzheng | | Wenjie Tian | WJTian | | Wenlong Chen | LeroChen | @@ -50,6 +53,7 @@ | Xiaonan chen | two | | Xiaoye Jiang | kidleaf-jiang | | Xin Li | lx-or-xxxl | +| Yang Guo | Marswin | | Yang Liu | dut-yangliu | | Yusheng Sun | wodedipanr | | Yuan Liu | lewisay | diff --git a/Dockerfile b/Dockerfile index 9ff02d9d1..c84410650 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,10 +6,10 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X main.version=`ca FROM alpine:3.10 AS run RUN apk update && apk add --no-cache ca-certificates -COPY --from=build /bfe/bfe /bfe/output/bin/ -COPY conf /bfe/output/conf/ +COPY --from=build /bfe/bfe /bfe/bin/ +COPY conf /bfe/conf/ EXPOSE 8080 8443 8421 -WORKDIR /bfe/output/bin +WORKDIR /bfe/bin ENTRYPOINT ["./bfe"] CMD ["-c", "../conf/", "-l", "../log"] diff --git a/bfe_basic/common.go b/bfe_basic/common.go index 66f6ff273..638dcba65 100644 --- a/bfe_basic/common.go +++ b/bfe_basic/common.go @@ -29,6 +29,10 @@ const ( HeaderRealPort = "X-Real-Port" ) +const ( + BfeVarsKey = "bfe-vars-key" +) + type OperationStage int const ( diff --git a/bfe_basic/condition/build.go b/bfe_basic/condition/build.go index 5413b8119..19d1bd16a 100644 --- a/bfe_basic/condition/build.go +++ b/bfe_basic/condition/build.go @@ -503,6 +503,35 @@ func buildPrimitive(node *parser.CallExpr) (Condition, error) { fetcher: &ClientCANameFetcher{}, matcher: NewInMatcher(node.Args[0].Value, false), }, nil + case "req_path_with_vars_in": + matcher, err := NewPathVariableMatcher(node.Args[0].Value, false) + if err != nil { + return nil, err + } + return &VariableCond{ + name: node.Fun.Name, + node: node, + fetcher: &PathFetcher{}, + matcher: matcher, + }, nil + case "req_path_with_vars_prefix_in": + matcher, err := NewPathVariableMatcher(node.Args[0].Value, true) + if err != nil { + return nil, err + } + return &VariableCond{ + name: node.Fun.Name, + node: node, + fetcher: &PathFetcher{}, + matcher: matcher, + }, nil + case "req_context_value_in": + return &PrimitiveCond{ + name: node.Fun.Name, + node: node, + fetcher: &ContextValueFetcher{node.Args[0].Value}, + matcher: NewInMatcher(node.Args[1].Value, false), + }, nil default: return nil, fmt.Errorf("unsupported primitive %s", node.Fun.Name) } diff --git a/bfe_basic/condition/parser/semant.go b/bfe_basic/condition/parser/semant.go index 830bac216..0413b606f 100644 --- a/bfe_basic/condition/parser/semant.go +++ b/bfe_basic/condition/parser/semant.go @@ -23,57 +23,60 @@ import ( // funcProtos holds a mapping from func name to args types. var funcProtos = map[string][]Token{ - "default_t": nil, - "req_cip_trusted": nil, - "req_vip_in": {STRING}, - "req_proto_match": {STRING}, - "req_proto_secure": nil, - "req_host_in": {STRING}, - "req_host_regmatch": {STRING}, - "req_host_tag_in": {STRING}, - "req_host_suffix_in": {STRING}, - "req_path_in": {STRING, BOOL}, - "req_path_prefix_in": {STRING, BOOL}, - "req_path_suffix_in": {STRING, BOOL}, - "req_path_regmatch": {STRING}, - "req_query_key_prefix_in": {STRING}, - "req_query_key_in": {STRING}, - "req_query_exist": nil, - "req_query_value_in": {STRING, STRING, BOOL}, - "req_query_value_prefix_in": {STRING, STRING, BOOL}, - "req_query_value_suffix_in": {STRING, STRING, BOOL}, - "req_query_value_regmatch": {STRING, STRING}, - "req_query_value_contain": {STRING, STRING, BOOL}, - "req_query_value_hash_in": {STRING, STRING, BOOL}, - "req_url_regmatch": {STRING}, - "req_cookie_key_in": {STRING}, - "req_cookie_value_in": {STRING, STRING, BOOL}, - "req_cookie_value_prefix_in": {STRING, STRING, BOOL}, - "req_cookie_value_suffix_in": {STRING, STRING, BOOL}, - "req_cookie_value_contain": {STRING, STRING, BOOL}, - "req_cookie_value_hash_in": {STRING, STRING, BOOL}, - "req_port_in": {STRING}, - "req_tag_match": {STRING, STRING}, - "req_ua_regmatch": {STRING}, - "req_header_key_in": {STRING}, - "req_header_value_in": {STRING, STRING, BOOL}, - "req_header_value_prefix_in": {STRING, STRING, BOOL}, - "req_header_value_suffix_in": {STRING, STRING, BOOL}, - "req_header_value_regmatch": {STRING, STRING}, - "req_header_value_contain": {STRING, STRING, BOOL}, - "req_header_value_hash_in": {STRING, STRING, BOOL}, - "req_method_in": {STRING}, - "req_cip_range": {STRING, STRING}, - "req_vip_range": {STRING, STRING}, - "req_cip_hash_in": {STRING}, - "res_code_in": {STRING}, - "res_header_key_in": {STRING}, - "res_header_value_in": {STRING, STRING, BOOL}, - "ses_vip_range": {STRING, STRING}, - "ses_sip_range": {STRING, STRING}, - "ses_tls_sni_in": {STRING}, - "ses_tls_client_auth": nil, - "ses_tls_client_ca_in": {STRING}, + "default_t": nil, + "req_cip_trusted": nil, + "req_vip_in": {STRING}, + "req_proto_match": {STRING}, + "req_proto_secure": nil, + "req_host_in": {STRING}, + "req_host_regmatch": {STRING}, + "req_host_tag_in": {STRING}, + "req_host_suffix_in": {STRING}, + "req_path_in": {STRING, BOOL}, + "req_path_prefix_in": {STRING, BOOL}, + "req_path_suffix_in": {STRING, BOOL}, + "req_path_regmatch": {STRING}, + "req_query_key_prefix_in": {STRING}, + "req_query_key_in": {STRING}, + "req_query_exist": nil, + "req_query_value_in": {STRING, STRING, BOOL}, + "req_query_value_prefix_in": {STRING, STRING, BOOL}, + "req_query_value_suffix_in": {STRING, STRING, BOOL}, + "req_query_value_regmatch": {STRING, STRING}, + "req_query_value_contain": {STRING, STRING, BOOL}, + "req_query_value_hash_in": {STRING, STRING, BOOL}, + "req_url_regmatch": {STRING}, + "req_cookie_key_in": {STRING}, + "req_cookie_value_in": {STRING, STRING, BOOL}, + "req_cookie_value_prefix_in": {STRING, STRING, BOOL}, + "req_cookie_value_suffix_in": {STRING, STRING, BOOL}, + "req_cookie_value_contain": {STRING, STRING, BOOL}, + "req_cookie_value_hash_in": {STRING, STRING, BOOL}, + "req_port_in": {STRING}, + "req_tag_match": {STRING, STRING}, + "req_ua_regmatch": {STRING}, + "req_header_key_in": {STRING}, + "req_header_value_in": {STRING, STRING, BOOL}, + "req_header_value_prefix_in": {STRING, STRING, BOOL}, + "req_header_value_suffix_in": {STRING, STRING, BOOL}, + "req_header_value_regmatch": {STRING, STRING}, + "req_header_value_contain": {STRING, STRING, BOOL}, + "req_header_value_hash_in": {STRING, STRING, BOOL}, + "req_method_in": {STRING}, + "req_cip_range": {STRING, STRING}, + "req_vip_range": {STRING, STRING}, + "req_cip_hash_in": {STRING}, + "res_code_in": {STRING}, + "res_header_key_in": {STRING}, + "res_header_value_in": {STRING, STRING, BOOL}, + "ses_vip_range": {STRING, STRING}, + "ses_sip_range": {STRING, STRING}, + "ses_tls_sni_in": {STRING}, + "ses_tls_client_auth": nil, + "ses_tls_client_ca_in": {STRING}, + "req_path_with_vars_in": {STRING}, + "req_path_with_vars_prefix_in": {STRING}, + "req_context_value_in": {STRING, STRING}, } func prototypeCheck(expr *CallExpr) error { diff --git a/bfe_basic/condition/primitive.go b/bfe_basic/condition/primitive.go index 1a1eb413f..c632bbf5b 100644 --- a/bfe_basic/condition/primitive.go +++ b/bfe_basic/condition/primitive.go @@ -926,3 +926,15 @@ func (fetcher *ClientCANameFetcher) Fetch(req *bfe_basic.Request) (interface{}, return req.Session.TlsState.ClientCAName, nil } + +type ContextValueFetcher struct { + key string +} + +func (f *ContextValueFetcher) Fetch(req *bfe_basic.Request) (interface{}, error) { + if req == nil || req.HttpRequest == nil || req.Context == nil || f.key == "" { + return nil, fmt.Errorf("fetcher: nil pointer") + } + + return req.GetContext(f.key), nil +} diff --git a/bfe_basic/condition/primitive_test.go b/bfe_basic/condition/primitive_test.go index 6088520a9..baf52d1f1 100644 --- a/bfe_basic/condition/primitive_test.go +++ b/bfe_basic/condition/primitive_test.go @@ -221,3 +221,23 @@ func TestContainMatcher_2(t *testing.T) { t.Fatalf("should match Yingwen") } } + +// test ContextFetcher +func TestContextValueFetcher(t *testing.T) { + // prepare input data + hf := ContextValueFetcher{"hello"} + req := bfe_basic.NewRequest(nil, nil, nil, nil, nil) + req.HttpRequest = &bfe_http.Request{} + req.SetContext("hello", "world") + // Fetch + contextVal, err := hf.Fetch(req) + if err != nil { + t.Fatalf("Fetch(): %v", err) + t.FailNow() + } + + // check + if contextVal.(string) != "world" { + t.Errorf("Fetch contextVal error, want=%v, got=%v", "world", contextVal) + } +} diff --git a/bfe_basic/condition/variable.go b/bfe_basic/condition/variable.go new file mode 100644 index 000000000..ef2fadfa3 --- /dev/null +++ b/bfe_basic/condition/variable.go @@ -0,0 +1,262 @@ +// Copyright (c) 2020 The BFE Authors. +// +// 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. + +// variable condition implementation +/** +Add variable condition and set in bfe`s condition match; +Diff from primitive condition which fetch and match, variable condition deal request in three steps: +- fetch info from request; +- match request with the fetched info +- extract variable from request +Here is the example: +`req_path_with_vars_in("/users/{userId}")` and request's path is /users/123, then the steps are: +- get path from request. path=/users/123; +- match path. /users/123 match /users/{userId}; +- extract vars. get vars userId=123 and store vars in request context(detail see: req.SetVar); +*/ + +package condition + +import ( + "bytes" + "fmt" + "regexp" + "strconv" +) + +import ( + "github.com/bfenetworks/bfe/bfe_basic" + "github.com/bfenetworks/bfe/bfe_basic/condition/parser" +) + +// VariableMatcher, which not only match request bu also extract vars from request; +type VariableMatcher interface { + Match(interface{}) bool // match func + ExtractVariable(interface{}) map[string]interface{} //extract func +} + +// VariableCond, diff from primitive cond, which also extract and store vars +type VariableCond struct { + name string + node *parser.CallExpr + fetcher Fetcher + matcher VariableMatcher +} + +func (c *VariableCond) String() string { + return c.node.String() +} + +func (c *VariableCond) Match(req *bfe_basic.Request) bool { + if req == nil || req.Session == nil || req.HttpRequest == nil || req.Context == nil { + return false + } + + // step1: fetch + fetched, err := c.fetcher.Fetch(req) + if err != nil { + return false + } + + // step2: match + matched := c.matcher.Match(fetched) + if !matched { + return false + } + // step3: extract and store + vars := c.matcher.ExtractVariable(fetched) + if vars == nil { + return false + } + c.setRequestVars(req, vars) + return true +} + +func (c *VariableCond) setRequestVars(req *bfe_basic.Request, vars map[string]interface{}) { + for key, value := range vars { + req.SetVar(key, value) + } +} + +// variableRegexp, store the original str and the regexp after build +type variableRegexp struct { + originalStr string // original str which record the str we want to build; lke /user/{userId} + patternStr string // pattern str which record the str we used to build; like /user/(?P[^/]+); + varNames []string // varNames which record the name of user defined; like ["userId"] + regexp *regexp.Regexp //regexp which is the regexp after build the pattern str; +} + +func (reg *variableRegexp) Match(s string) bool { + if reg == nil || reg.regexp == nil { + return false + } + return reg.regexp.MatchString(s) +} + +func (reg *variableRegexp) ExtractVariable(s string) map[string]interface{} { + if reg == nil || reg.regexp == nil { + return nil + } + + vIndex := reg.regexp.FindStringSubmatchIndex(s) + + if vIndex == nil { + return nil + } + + if len(reg.varNames) != len(vIndex)/2-1 { + return nil + } + + var varMap = make(map[string]interface{}) + for i, name := range reg.varNames { + varMap[name] = s[vIndex[2*i+2]:vIndex[2*i+3]] + } + return varMap +} + +func NewPathVariableMatcher(tpl string, isPrefixType bool) (VariableMatcher, error) { + p := new(pathVariableMatcher) + err := p.Init(tpl, isPrefixType) + if err != nil { + return nil, err + } + return p, err +} + +type pathVariableMatcher struct { + vRegexp *variableRegexp +} + +func (p *pathVariableMatcher) Init(tpl string, isPrefixType bool) error { + vRegexp, err := buildPathVariableRegexp(tpl, isPrefixType) + if err != nil { + return err + } + p.vRegexp = vRegexp + return nil +} + +func (p *pathVariableMatcher) Match(path interface{}) bool { + pathStr, ok := path.(string) + if !ok { + return false + } + if p.vRegexp == nil || p.vRegexp.regexp == nil { + return false + } + return p.vRegexp.Match(pathStr) +} + +func (p *pathVariableMatcher) ExtractVariable(path interface{}) map[string]interface{} { + pathStr, ok := path.(string) + if !ok { + return nil + } + if p.vRegexp == nil { + return nil + } + return p.vRegexp.ExtractVariable(pathStr) +} + +func braceIndices(s string) ([]int, error) { + var indices = make([]int, 0) + var err = fmt.Errorf("braceIndices() error: invalid brace sequence[%s]", s) + left, right := 0, 1 + braceIndex := left + for i := 0; i < len(s); i++ { + switch s[i] { + case '{': + if braceIndex == left { + indices = append(indices, i) + braceIndex = right + } else { + return nil, err + } + case '}': + if braceIndex == right { + indices = append(indices, i) + braceIndex = left + } else { + return nil, err + } + } + } + if braceIndex != left { + return nil, err + } + return indices, nil +} + +// buildPathVariableRegexp is used to build varRegexp. This function is inspired by gorilla/mux. +// tpl is the str like /user/{userId}, note: we just support to use brace to wrapper variable name. +// here is the brief description: +// 1. get all the location of char '{' and '}' +// 2. get the raw str before '{', varName between '{' and '}' +// 3. varname store in varNames and we change the varname to regexp str '(?P[^/]+)', note: avoid of some unexpected +// groupName, we user vN as the variable group name, like v0, v1 +// 4. we build the regexp str to build variableRegexp +func buildPathVariableRegexp(tpl string, isPrefixType bool) (*variableRegexp, error) { + braces, err := braceIndices(tpl) + if err != nil { + return nil, err + } + + pattern := bytes.NewBufferString("") + pattern.WriteByte('^') + defaultRegPattern := "[^/]+" + + headerIndex := 0 + varNameMap := make(map[string]bool) + + var vars = make([]string, len(braces)/2) + for i := 0; i < len(braces)/2; i++ { + leftBraceIndex := braces[2*i] + rightBraceIndex := braces[2*i+1] + + rawStr := tpl[headerIndex:leftBraceIndex] + varName := tpl[leftBraceIndex+1 : rightBraceIndex] + + if varName == "" { + return nil, fmt.Errorf("buildPathVariableRegexp() empty varname, tpl: %s", tpl) + } + if _, exists := varNameMap[varName]; exists { + return nil, fmt.Errorf("buildPathVariableRegexp() duplicate varname, tpl: %s", tpl) + } + varNameMap[varName] = true + + headerIndex = rightBraceIndex + 1 + vars[i] = varName + patternV := "v" + strconv.Itoa(i) + fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(rawStr), patternV, defaultRegPattern) + } + + endStr := tpl[headerIndex:] + fmt.Fprintf(pattern, "%s", endStr) + + if !isPrefixType { + fmt.Fprintf(pattern, "$") + } + + reg, err := regexp.Compile(pattern.String()) + if err != nil { + return nil, err + } + return &variableRegexp{ + originalStr: tpl, + varNames: vars, + regexp: reg, + patternStr: pattern.String(), + }, nil +} diff --git a/bfe_basic/condition/variable_test.go b/bfe_basic/condition/variable_test.go new file mode 100644 index 000000000..f86b1ca8a --- /dev/null +++ b/bfe_basic/condition/variable_test.go @@ -0,0 +1,556 @@ +// Copyright (c) 2020 The BFE Authors. +// +// 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 condition + +import ( + "reflect" + "regexp" + "testing" +) + +import ( + "github.com/bfenetworks/bfe/bfe_basic" + "github.com/bfenetworks/bfe/bfe_http" +) + +func Test_pathVariableMatcher_Match(t *testing.T) { + type args struct { + rule string + path string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "normal test", + args: args{ + rule: "/teams/{teamId}/users/{userId}", + path: "/teams/1/users/3", + }, + want: true, + }, + { + name: "invalid path", + args: args{ + rule: "/teams/{teamId}/users/{userId}", + path: "/teams/1/users_test/3", + }, + want: false, + }, + { + name: "path with /", + args: args{ + rule: "/teams/{teamId}/users/{userId}", + path: "/teams/1/users/3/", + }, + want: false, + }, + { + name: "rule with /", + args: args{ + rule: "/teams/{teamId}/users/{userId}/", + path: "/teams/1/users/3", + }, + want: false, + }, + { + name: "rule and path with /", + args: args{ + rule: "/teams/{teamId}/users/{userId}/", + path: "/teams/1/users/3/", + }, + want: true, + }, + { + name: "not start with rule", + args: args{ + rule: "/teams/{teamId}/users/{userId}", + path: "/api/v1/teams/1/users/3", + }, + want: false, + }, + { + name: "not end with rule", + args: args{ + rule: "/teams/{teamId}/users/{userId}", + path: "/teams/1/users/3/info/3", + }, + want: false, + }, + { + name: "not end with rule", + args: args{ + rule: "/teams/teamId/users/userId", + path: "/teams/teamId/users/userId", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := new(pathVariableMatcher) + p.Init(tt.args.rule, false) + if got := p.Match(tt.args.path); got != tt.want { + t.Errorf("Match() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_braceIndices(t *testing.T) { + type args struct { + s string + } + tests := []struct { + name string + args args + want []int + wantErr bool + }{ + { + name: "test normal", + args: args{ + s: "/user/{userId}", + }, + want: []int{6, 13}, + wantErr: false, + }, + { + name: "test no brace", + args: args{ + s: "/user/userId", + }, + want: []int{}, + wantErr: false, + }, + { + name: "test no left brace", + args: args{ + s: "/user/{userId", + }, + want: nil, + wantErr: true, + }, + { + name: "test no right brace", + args: args{ + s: "/user/userId}", + }, + want: nil, + wantErr: true, + }, + { + name: "test no err seq brace", + args: args{ + s: "/user/u}serId{", + }, + want: nil, + wantErr: true, + }, + { + name: "test multi left brace", + args: args{ + s: "/user/{{serId}", + }, + want: nil, + wantErr: true, + }, + { + name: "test no multi brace", + args: args{ + s: "/user/{{serId}}", + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := braceIndices(tt.args.s) + if (err != nil) != tt.wantErr { + t.Errorf("braceIndices() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("braceIndices() got = %v, want %v", got, tt.want) + } + }) + } +} + +func getExp(tpl string) *regexp.Regexp { + ret, _ := regexp.Compile(tpl) + return ret +} + +func Test_buildPathVariableRegexp(t *testing.T) { + type args struct { + tpl string + isPrefixType bool + } + tests := []struct { + name string + args args + want *variableRegexp + wantErr bool + }{ + { + name: "test no prefix", + args: args{ + tpl: "/user/{userId}", + isPrefixType: false, + }, + want: &variableRegexp{ + originalStr: "/user/{userId}", + patternStr: "^/user/(?P[^/]+)$", + varNames: []string{"userId"}, + regexp: getExp("^/user/(?P[^/]+)$"), + }, + wantErr: false, + }, + { + name: "test prefix", + args: args{ + tpl: "/user/{userId}", + isPrefixType: true, + }, + want: &variableRegexp{ + originalStr: "/user/{userId}", + patternStr: "^/user/(?P[^/]+)", + varNames: []string{"userId"}, + regexp: getExp("^/user/(?P[^/]+)"), + }, + wantErr: false, + }, + { + name: "test invalid params", + args: args{ + tpl: "/user/{userId", + isPrefixType: false, + }, + want: nil, + wantErr: true, + }, + { + name: "test invalid params", + args: args{ + tpl: "/user/{userId}/info/{ss", + isPrefixType: false, + }, + want: nil, + wantErr: true, + }, + { + name: "test invalid params", + args: args{ + tpl: "/user/{userId}/info/{", + isPrefixType: false, + }, + want: nil, + wantErr: true, + }, + { + name: "test invalid params", + args: args{ + tpl: "/user/{userId}/info/}", + isPrefixType: false, + }, + want: nil, + wantErr: true, + }, + { + name: "test invalid params", + args: args{ + tpl: "/user/{userId}/info/{", + isPrefixType: true, + }, + want: nil, + wantErr: true, + }, + { + name: "test with regexp in raw str", + args: args{ + tpl: "/u.*/{userId}/in.*/{infoId}", + isPrefixType: true, + }, + want: &variableRegexp{ + originalStr: "/u.*/{userId}/in.*/{infoId}", + patternStr: "^/u\\.\\*/(?P[^/]+)/in\\.\\*/(?P[^/]+)", + varNames: []string{"userId", "infoId"}, + regexp: getExp("^/u\\.\\*/(?P[^/]+)/in\\.\\*/(?P[^/]+)"), + }, + wantErr: false, + }, + { + name: "test without params", + args: args{ + tpl: "/user/userId/info/infoId", + isPrefixType: true, + }, + want: &variableRegexp{ + originalStr: "/user/userId/info/infoId", + patternStr: "^/user/userId/info/infoId", + varNames: []string{}, + regexp: getExp("^/user/userId/info/infoId"), + }, + wantErr: false, + }, + { + name: "test without params with regexp", + args: args{ + tpl: "/user/userId/info/infoId", + isPrefixType: true, + }, + want: &variableRegexp{ + originalStr: "/user/userId/info/infoId", + patternStr: "^/user/userId/info/infoId", + varNames: []string{}, + regexp: getExp("^/user/userId/info/infoId"), + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := buildPathVariableRegexp(tt.args.tpl, tt.args.isPrefixType) + if (err != nil) != tt.wantErr { + t.Errorf("buildPathVariableRegexp() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("buildPathVariableRegexp() got = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_pathVariableMatcher_ExtractVariable(t *testing.T) { + type args struct { + rule string + path string + } + tests := []struct { + name string + args args + want map[string]interface{} + }{ + { + name: "test normal", + args: args{ + rule: "/user/{userId}/info/{infoId}", + path: "/user/123/info/456", + }, + want: map[string]interface{}{ + "userId": "123", + "infoId": "456", + }, + }, + { + name: "test no vars", + args: args{ + rule: "/user/123/info/456", + path: "/user/123/info/456", + }, + want: map[string]interface{}{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p, err := NewPathVariableMatcher(tt.args.rule, false) + if err != nil { + t.Errorf("ExtractVariable() err= %v", err) + return + } + if got := p.ExtractVariable(tt.args.path); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ExtractVariable() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_VariableCond_Match(t *testing.T) { + matcher, _ := NewPathVariableMatcher("/user/{userId}/info/{infoId}", false) + needVars := map[string]interface{}{ + "userId": "123", + "infoId": "456", + } + vc := VariableCond{ + name: "", + node: nil, + fetcher: &PathFetcher{}, + matcher: matcher, + } + // not prefix type + req := new(bfe_basic.Request) + req.Session = new(bfe_basic.Session) + req.HttpRequest, _ = bfe_http.NewRequest("GET", "http://example.org/user/123/info/456", nil) + req.Context = make(map[interface{}]interface{}) + + matched := vc.Match(req) + if !matched { + t.Errorf("request should match") + } else { + if !reflect.DeepEqual(needVars, req.Context[bfe_basic.BfeVarsKey]) { + t.Errorf("Match() got=%v, want=%v", req.Context[bfe_basic.BfeVarsKey], needVars) + } + for key, value := range needVars { + if value != req.GetVar(key) { + t.Errorf("Match() got=%v, want=%v", req.GetVar(key), value) + } + } + } + + // prefix path + req = new(bfe_basic.Request) + req.Session = new(bfe_basic.Session) + req.HttpRequest, _ = bfe_http.NewRequest("GET", "http://example.org/user/123/info/456/path", nil) + req.Context = make(map[interface{}]interface{}) + + matched = vc.Match(req) + if matched { + t.Errorf("request should not match") + } + + // prefix mod + matcher, _ = NewPathVariableMatcher("/user/{userId}/info/{infoId}", true) + needVars = map[string]interface{}{ + "userId": "123", + "infoId": "456", + } + vc = VariableCond{ + name: "", + node: nil, + fetcher: &PathFetcher{}, + matcher: matcher, + } + req = new(bfe_basic.Request) + req.Session = new(bfe_basic.Session) + req.HttpRequest, _ = bfe_http.NewRequest("GET", "http://example.org/user/123/info/456", nil) + req.Context = make(map[interface{}]interface{}) + + matched = vc.Match(req) + if !matched { + t.Errorf("request should match") + } else { + if !reflect.DeepEqual(needVars, req.Context[bfe_basic.BfeVarsKey]) { + t.Errorf("Match() got=%v, want=%v", req.Context[bfe_basic.BfeVarsKey], needVars) + } + } + + req = new(bfe_basic.Request) + req.Session = new(bfe_basic.Session) + req.HttpRequest, _ = bfe_http.NewRequest("GET", "http://example.org/user/123/info/456/", nil) + req.Context = make(map[interface{}]interface{}) + + matched = vc.Match(req) + if !matched { + t.Errorf("request should match") + } else { + if !reflect.DeepEqual(needVars, req.Context[bfe_basic.BfeVarsKey]) { + t.Errorf("Match() got=%v, want=%v", req.Context[bfe_basic.BfeVarsKey], needVars) + } + } + + req = new(bfe_basic.Request) + req.Session = new(bfe_basic.Session) + req.HttpRequest, _ = bfe_http.NewRequest("GET", "http://example.org/user/123/info/456/path", nil) + req.Context = make(map[interface{}]interface{}) + + matched = vc.Match(req) + if !matched { + t.Errorf("request should match") + } else { + if !reflect.DeepEqual(needVars, req.Context[bfe_basic.BfeVarsKey]) { + t.Errorf("Match() got=%v, want=%v", req.Context[bfe_basic.BfeVarsKey], needVars) + } + } + + // no varibale + matcher, _ = NewPathVariableMatcher("/user/userId/info/infoId", true) + vc = VariableCond{ + name: "", + node: nil, + fetcher: &PathFetcher{}, + matcher: matcher, + } + req = new(bfe_basic.Request) + req.Session = new(bfe_basic.Session) + req.HttpRequest, _ = bfe_http.NewRequest("GET", "http://example.org/user/userId/info/infoId", nil) + req.Context = make(map[interface{}]interface{}) + + matched = vc.Match(req) + if !matched { + t.Errorf("request should match") + } else { + if !reflect.DeepEqual(nil, req.Context[bfe_basic.BfeVarsKey]) { + t.Errorf("Match() got=%v, want=%v", req.Context[bfe_basic.BfeVarsKey], needVars) + } + } + + // match raw str with reg + matcher, _ = NewPathVariableMatcher("/u.*/{userId}/i.*/{infoId}", true) + needVars = map[string]interface{}{ + "userId": "123", + "infoId": "456", + } + vc = VariableCond{ + name: "", + node: nil, + fetcher: &PathFetcher{}, + matcher: matcher, + } + req = new(bfe_basic.Request) + req.Session = new(bfe_basic.Session) + req.HttpRequest, _ = bfe_http.NewRequest("GET", "http://example.org/u.*/123/i.*/456", nil) + req.Context = make(map[interface{}]interface{}) + + matched = vc.Match(req) + if !matched { + t.Errorf("request should match") + } else { + if !reflect.DeepEqual(needVars, req.Context[bfe_basic.BfeVarsKey]) { + t.Errorf("Match() got=%v, want=%v", req.Context[bfe_basic.BfeVarsKey], needVars) + } + } + + req = new(bfe_basic.Request) + req.Session = new(bfe_basic.Session) + req.HttpRequest, _ = bfe_http.NewRequest("GET", "http://example.org/user/123/info/456", nil) + req.Context = make(map[interface{}]interface{}) + + matched = vc.Match(req) + if matched { + t.Errorf("request should not match") + } + + // not with context + matcher, _ = NewPathVariableMatcher("/u.*/{userId}/i.*/{infoId}", true) + vc = VariableCond{ + name: "", + node: nil, + fetcher: &PathFetcher{}, + matcher: matcher, + } + req = new(bfe_basic.Request) + req.Session = new(bfe_basic.Session) + req.HttpRequest, _ = bfe_http.NewRequest("GET", "http://example.org/u.*/123/i.*/456", nil) + + matched = vc.Match(req) + if matched { + t.Errorf("request should not match") + } +} diff --git a/bfe_basic/request.go b/bfe_basic/request.go index db375015e..e6a947472 100644 --- a/bfe_basic/request.go +++ b/bfe_basic/request.go @@ -161,24 +161,49 @@ func (req *Request) Protocol() string { return req.HttpRequest.Proto } -func (r *Request) AddTags(name string, ntags []string) { +func (req *Request) AddTags(name string, ntags []string) { if len(ntags) == 0 { return } - tags := r.Tags.TagTable[name] + tags := req.Tags.TagTable[name] tags = append(tags, ntags...) - r.Tags.TagTable[name] = tags + req.Tags.TagTable[name] = tags } -func (r *Request) GetTags(name string) []string { - return r.Tags.TagTable[name] +func (req *Request) GetTags(name string) []string { + return req.Tags.TagTable[name] } -func (r *Request) SetContext(key, val interface{}) { - r.Context[key] = val +func (req *Request) SetContext(key, val interface{}) { + req.Context[key] = val } -func (r *Request) GetContext(key interface{}) interface{} { - return r.Context[key] +func (req *Request) GetContext(key interface{}) interface{} { + return req.Context[key] +} + +func (r *Request) SetVar(key string, val interface{}) { + v := r.GetContext(BfeVarsKey) + if v == nil { + v = make(map[string]interface{}) + } + vars, ok := v.(map[string]interface{}) + if !ok { + vars = make(map[string]interface{}) + } + vars[key] = val + r.SetContext(BfeVarsKey, vars) +} + +func (r *Request) GetVar(key string) interface{} { + v := r.GetContext(BfeVarsKey) + if v == nil { + return nil + } + vars, ok := v.(map[string]interface{}) + if !ok { + return nil + } + return vars[key] } diff --git a/bfe_config/bfe_cluster_conf/cluster_table_conf/cluster_table_load.go b/bfe_config/bfe_cluster_conf/cluster_table_conf/cluster_table_load.go index 26715a0ad..629597ca8 100644 --- a/bfe_config/bfe_cluster_conf/cluster_table_conf/cluster_table_load.go +++ b/bfe_config/bfe_cluster_conf/cluster_table_conf/cluster_table_load.go @@ -162,9 +162,9 @@ func (conf *AllClusterBackend) Check() error { return AllClusterBackendCheck(conf) } -func (sub *SubClusterBackend) Check() error { +func (s *SubClusterBackend) Check() error { availBackend := false - for index, backendConf := range *sub { + for index, backendConf := range *s { err := BackendConfCheck(backendConf) if err != nil { diff --git a/bfe_modules/mod_geo/conf_mod_geo_test.go b/bfe_modules/mod_geo/conf_mod_geo_test.go index 21e45fe90..9dd8117ee 100644 --- a/bfe_modules/mod_geo/conf_mod_geo_test.go +++ b/bfe_modules/mod_geo/conf_mod_geo_test.go @@ -19,7 +19,7 @@ import ( "testing" ) -func TestConfModGeoCase1(t *testing.T) { +func TestConfModGeo(t *testing.T) { config, err := ConfLoad("./test_data/mod_geo/mod_geo.conf", "") if err != nil { msg := fmt.Sprintf("confModGeoLoad():err=%s", err.Error()) @@ -32,8 +32,8 @@ func TestConfModGeoCase1(t *testing.T) { } } -func TestConfModGeoCase2(t *testing.T) { - config, err := ConfLoad("./test_data/mod_geo/mod_geo1.conf", "") +func TestConfModGeoDefaultPath(t *testing.T) { + config, err := ConfLoad("./test_data/mod_geo/mod_geo.conf.default_path", "") if err != nil { msg := fmt.Sprintf("confModGeoLoad():err=%s", err.Error()) t.Error(msg) diff --git a/bfe_modules/mod_geo/test_data/mod_geo/mod_geo1.conf b/bfe_modules/mod_geo/test_data/mod_geo/mod_geo.conf.default_path similarity index 100% rename from bfe_modules/mod_geo/test_data/mod_geo/mod_geo1.conf rename to bfe_modules/mod_geo/test_data/mod_geo/mod_geo.conf.default_path diff --git a/bfe_modules/mod_prison/mod_prison.go b/bfe_modules/mod_prison/mod_prison.go index 7f034f7a6..8d1fc3c0b 100644 --- a/bfe_modules/mod_prison/mod_prison.go +++ b/bfe_modules/mod_prison/mod_prison.go @@ -66,7 +66,6 @@ type ModulePrison struct { state ModulePrisonState // module state metrics metrics.Metrics productConfPath string // path for prodct rule - conf ProductRuleConf // config for prison productTable *productRuleTable // product rule table } diff --git a/bfe_modules/mod_prison/rule_test.go b/bfe_modules/mod_prison/rule_test.go index 49da3d3cf..b4a42118e 100644 --- a/bfe_modules/mod_prison/rule_test.go +++ b/bfe_modules/mod_prison/rule_test.go @@ -191,14 +191,14 @@ func TestRecordAccess(t *testing.T) { // meet threshod, should be zero rule.recordAccess(sign) - value, ok = rule.accessDict.Get(sign) + _, ok = rule.accessDict.Get(sign) if ok { t.Errorf("access counter should be deleted") return } // should get failed - value, ok = rule.accessDict.Get(AccessSign(md5.Sum([]byte("1234")))) + _, ok = rule.accessDict.Get(AccessSign(md5.Sum([]byte("1234")))) if ok { t.Error("should get failed") return diff --git a/bfe_util/signal_table/signal_table.go b/bfe_util/signal_table/signal_table.go index 9e7f8a755..bb8445fd0 100644 --- a/bfe_util/signal_table/signal_table.go +++ b/bfe_util/signal_table/signal_table.go @@ -59,10 +59,10 @@ func (t *SignalTable) handle(sig os.Signal) { } // signalHandle is the signal handle loop -func (table *SignalTable) signalHandle() { +func (t *SignalTable) signalHandle() { var sigs []os.Signal - for sig := range table.shs { + for sig := range t.shs { sigs = append(sigs, sig) } @@ -71,7 +71,7 @@ func (table *SignalTable) signalHandle() { for { sig := <-c - table.handle(sig) + t.handle(sig) } } diff --git a/docs/en_us/SUMMARY.md b/docs/en_us/SUMMARY.md index abdc23241..b80653897 100644 --- a/docs/en_us/SUMMARY.md +++ b/docs/en_us/SUMMARY.md @@ -18,6 +18,7 @@ * [Traffic blocking](example/block.md) * [Request redirect](example/redirect.md) * [Request rewrite](example/rewrite.md) + * [FastCGI procotol](example/fastcgi.md) * [TLS mutual authentication](example/client_auth.md) * [Installation](installation/install.md) * [Install from source](installation/install_from_source.md) diff --git a/docs/en_us/example/fastcgi.md b/docs/en_us/example/fastcgi.md new file mode 100644 index 000000000..20fb64a8f --- /dev/null +++ b/docs/en_us/example/fastcgi.md @@ -0,0 +1,151 @@ +# FastCGI protocol + +## Scenario + +* Imagine we have an http server which has two instances. One is responsible for processing fcgi protocol requests, and the other is responsible for http requests. + * Host:example.org + * Requests that start with / fcgi are forwarded to the fcgi protocol service instance with address 10.0.0.1:8001 + * Other requests are forwarded to http service instance with address 10.0.0.1:8002 + +## Configuration + +Modify example configurations (conf/) as the following steps: + +* Step 1. Config path of forward rules in conf/bfe.conf + +```ini +hostRuleConf = server_data_conf/host_rule.data +routeRuleConf = server_data_conf/route_rule.data +clusterConf = server_data_conf/cluster_conf.data + +clusterTableConf = cluster_conf/cluster_table.data +gslbConf = cluster_conf/gslb.data +``` + +* Step 2. Config host rules (conf/server_data_conf/host_rule.data) + +```json +{ + "Version": "init version", + "DefaultProduct": null, + "Hosts": { + "exampleTag":[ + "example.org" // host name: example.org=>host tag: exampleTag + ] + }, + "HostTags": { + "example_product":[ + "exampleTag" // host tag: exampleTag=>product name: example_product + ] + } +} +``` + +* Step 3. Config cluster configuration (conf/server_data_conf/cluster_conf.data) +Note: Set backend conf params and use default value for other params + +```json +{ + "Version": "init version", + "Config": { + "cluster_demo_http": { + "BackendConf": { + "TimeoutConnSrv": 2000, + "TimeoutResponseHeader": 50000, + "MaxIdleConnsPerHost": 0, + "RetryLevel": 0 + } + }, + "cluster_demo_fcgi": { + "BackendConf": { + "Protocol": "fcgi", + "TimeoutConnSrv": 2000, + "TimeoutResponseHeader": 50000, + "MaxIdleConnsPerHost": 0, + "RetryLevel": 0, + "FCGIConf": { + "Root": "/home/work", + "EnvVars": { + "VarKey": "VarVal" + } + } + } + } + } +} +``` + +* Step 4. Config instances of cluster (conf/cluster_conf/cluster_table.data) + +```json +{ + "Version": "init version", + "Config": { + "cluster_demo_fcgi": { // cluster => sub_cluster => instance list + "demo_fcgi.all": [{ // subcluster: demo_fcgi.all + "Addr": "10.0.0.1", + "Name": "fcgi.A", + "Port": 8001, + "Weight": 1 + }] + }, + "cluster_demo_http": { + "demo_http.all": [{ + "Addr": "10.0.0.1", + "Name": "http.A", + "Port": 8002, + "Weight": 1 + }] + } + } +} +``` + +* Step 5. Config gslb configuration (conf/cluster_conf/gslb.data) + +```json +{ + "Hostname": "", + "Ts": "0", + "Clusters": { + "cluster_demo_fcgi": { // cluster => weight of subcluster + "GSLB_BLACKHOLE": 0, // GSLB_BLACKHOLE == 0 means do not discard traffic + "demo_fcgi.all": 100 // weight 100 means all traffic routes to demo_fcgi.all + }, + "cluster_demo_http": { + "GSLB_BLACKHOLE": 0, + "demo_http.all": 100 + } + } +} +``` + +* Step 6. Config route rules (conf/server_data_conf/route_rule.data) + +```json +{ + "Version": "init version", + "ProductRule": { + "example_product": [ // product => route rules + { + "Cond": "req_path_prefix_in(\"/fcgi\", false)", + "ClusterName": "cluster_demo_fcgi" + }, + { + "Cond": "default_t()", + "ClusterName": "cluster_demo_http" + } + ] + } +} +``` + +* Step 7. Verify configured rules + +```bash +curl -H "host: example.org" "http://127.1:8080/fcgi/test" +# request will route to 10.0.0.1:8001 + +curl -H "host: example.org" "http://127.1:8080/http/test" +# request will route to 10.0.0.1:8002 +``` diff --git a/docs/en_us/installation/install.md b/docs/en_us/installation/install.md index 16f96ce82..018656e94 100644 --- a/docs/en_us/installation/install.md +++ b/docs/en_us/installation/install.md @@ -5,6 +5,7 @@ - [Install using binaries](install_using_binaries.md) - [Install using go](install_using_go.md) - [Install using snap](install_using_snap.md) +- [Install using docker](install_using_docker.md) ## Supported platform | Operating System | Description | diff --git a/docs/en_us/installation/install_using_docker.md b/docs/en_us/installation/install_using_docker.md new file mode 100644 index 000000000..2ed7b35cf --- /dev/null +++ b/docs/en_us/installation/install_using_docker.md @@ -0,0 +1,25 @@ +# Install using docker + +## Install && Run + +- Run BFE with example configuration files: + +```bash +docker run -p 8080:8080 -p 8443:8443 -p 8421:8421 bfenetworks/bfe +``` + +you can access http://127.0.0.1:8080/ and got status code 500 because of there is rule be matched. +you can access http://127.0.0.1:8421/ got monitor infomation. + + +- Run BFE with your configuration files: +```bash +// prepare your configuration (see section Configuration if you need) to dir /Users/BFE/conf + +docker run -p 8080:8080 -p 8443:8443 -p 8421:8421 -v /Users/BFE/Desktop/log:/bfe/log -v /Users/BFE/Desktop/conf:/bfe/conf bfenetworks/bfe +``` + +## Further reading +- Get familiar with [Command options](../operation/command.md) +- Get started with [Beginner's Guide](../example/guide.md) + diff --git a/docs/zh_cn/SUMMARY.md b/docs/zh_cn/SUMMARY.md index 565ec36d1..176d587ce 100644 --- a/docs/zh_cn/SUMMARY.md +++ b/docs/zh_cn/SUMMARY.md @@ -18,6 +18,7 @@ * [黑名单封禁](example/block.md) * [重定向](example/redirect.md) * [重写](example/rewrite.md) + * [FastCGI协议](example/fastcgi.md) * [TLS客户端认证](example/client_auth.md) * [安装说明](installation/install.md) * [源码编译安装](installation/install_from_source.md) diff --git a/docs/zh_cn/example/fastcgi.md b/docs/zh_cn/example/fastcgi.md new file mode 100644 index 000000000..830f93b7e --- /dev/null +++ b/docs/zh_cn/example/fastcgi.md @@ -0,0 +1,154 @@ +# FCGI协议 + +## 场景说明 + +* 假设我们有一个http server对外提供服务,并且有2个服务实例;1个负责处理FastCGI协议请求,另外1个负责处理HTTP协议请求 + * 域名:example.org + * 以/fcgi开头的请求都转发至FastCGI协议服务实例;地址:10.0.0.1:8001 + * 其他的请求都转发至HTTP协议服务实例;地址:10.0.0.1:8002 + +## 配置说明 +在[样例配置](../../../conf/)上稍做修改,就可以实现上述转发功能 + +* Step 1.在 conf/bfe.conf配置转发功能使用的配置文件路径 + +```ini +hostRuleConf = server_data_conf/host_rule.data #域名规则配置文件 +routeRuleConf = server_data_conf/route_rule.data #分流规则配置文件 +clusterConf = server_data_conf/cluster_conf.data #集群配置文件 + +clusterTableConf = cluster_conf/cluster_table.data #集群实例列表配置文件 +gslbConf = cluster_conf/gslb.data #子集群负载均衡配置文件 +``` + +* Step 2. 配置域名规则 (conf/server_data_conf/host_rule.data) + +```json +{ + "Version": "init version", + "DefaultProduct": null, + "Hosts": { + "exampleTag":[ + "example.org" // 域名example.org=>域名标签exampleTag + ] + }, + "HostTags": { + "example_product":[ + "exampleTag" // 域名标签exampleTag=>产品线名称example_product + ] + } +} +``` + +* Step 3. 配置集群的基础信息 (conf/server_data_conf/cluster_conf.data) +配置集群cluster_demo_fcgi和cluster_demo_http 后端配置的参数,其他均使用默认值 + +```json +{ + "Version": "init version", + "Config": { + "cluster_demo_http": { // 集群cluster_demo_http的配置 + "BackendConf": { + "TimeoutConnSrv": 2000, + "TimeoutResponseHeader": 50000, + "MaxIdleConnsPerHost": 0, + "RetryLevel": 0 + } + }, + "cluster_demo_fcgi": { // 集群cluster_demo_fcgi的配置 + "BackendConf": { + "Protocol": "fcgi", + "TimeoutConnSrv": 2000, + "TimeoutResponseHeader": 50000, + "MaxIdleConnsPerHost": 0, + "RetryLevel": 0, + "FCGIConf": { + "Root": "/home/work", + "EnvVars": { + "VarKey": "VarVal" + } + } + } + } + } +} +``` + +* Step 4. 配置集群下实例信息 (conf/cluster_conf/cluster_table.data) + +```json +{ + "Version": "init version", + "Config": { + "cluster_demo_fcgi": { // 集群 => 子集群 => 实例列表 + "demo_fcgi.all": [{ // 子集群demo_fcgi.all + "Addr": "10.0.0.1", // 实例地址:10.0.0.1 + "Name": "fcgi.A", // 实例名:fcgi.A + "Port": 8001, // 实例端口:8001 + "Weight": 1 // 实例权重:1 + }] + }, + "cluster_demo_http": { + "demo_http.all": [{ + "Addr": "10.0.0.1", + "Name": "http.A", + "Port": 8002, + "Weight": 1 + }] + } + } +} +``` + +* Step 5. 配置子集群内负载均衡 (conf/cluster_conf/gslb.data) + +```json +{ + "Hostname": "", + "Ts": "0", + "Clusters": { + "cluster_demo_fcgi": { // 集群 => 子集群权重 + "GSLB_BLACKHOLE": 0, // 黑洞的分流权重为0,表示不丢弃流量 + "demo_fcgi.all": 100 // 权重为100,表示全部分流到demo_fcgi.all + }, + "cluster_demo_http": { + "GSLB_BLACKHOLE": 0, + "demo_http.all": 100 + } + } +} +``` + +* Step 6. 配置分流规则 (conf/server_data_conf/route_rule.data) + * 将/fcgi开头的流量转发到cluster_demo_fcgi集群 + * 其余流量转发到cluster_demo_http集群 + +```json +{ + "Version": "init version", + "ProductRule": { + "example_product": [ // 产品线 => 分流规则 + { + // 以/fcgi开头的path分流到cluster_demo_fcgi集群 + "Cond": "req_path_prefix_in(\"/fcgi\", false)", + "ClusterName": "cluster_demo_fcgi" + }, + { + // 其他流量分流到cluster_demo_http集群 + "Cond": "default_t()", + "ClusterName": "cluster_demo_http" + } + ] + } +} +``` + +* Step 7. 验证配置规则 + +```bash +curl -H "host: example.org" "http://127.1:8080/fcgi/test" +# 将请求转发至10.0.0.1:8001 + +curl -H "host: example.org" "http://127.1:8080/http/test" +# 将请求转发至10.0.0.1:8002 +``` diff --git a/docs/zh_cn/installation/install.md b/docs/zh_cn/installation/install.md index 868553082..59aa05435 100644 --- a/docs/zh_cn/installation/install.md +++ b/docs/zh_cn/installation/install.md @@ -5,6 +5,7 @@ - [二进制文件下载安装](install_using_binaries.md) - [go方式安装](install_using_go.md) - [snap方式安装](install_using_snap.md) +- [docker方式安装](install_using_docker.md) ## 平台支持 | 操作系统 | 支持说明 | diff --git a/docs/zh_cn/installation/install_using_docker.md b/docs/zh_cn/installation/install_using_docker.md new file mode 100644 index 000000000..1056d549d --- /dev/null +++ b/docs/zh_cn/installation/install_using_docker.md @@ -0,0 +1,25 @@ +# docker安装 + + +## 安装 && 运行 + +- 基于示例配置运行BFE: + +```bash +docker run -p 8080:8080 -p 8443:8443 -p 8421:8421 bfenetworks/bfe +``` + +你可以访问 http://127.0.0.1:8080/ 因为没有匹配的配置,将得到 status code 500 +你可以访问 http://127.0.0.1:8421/ 查看监控信息 + +- 自定义配置文件路径 + +```bash +// 事先准备好你自己的配置放到 (可以参考 配置 章节) /Users/BFE/conf + +docker run -p 8080:8080 -p 8443:8443 -p 8421:8421 -v /Users/BFE/Desktop/log:/bfe/log -v /Users/BFE/Desktop/conf:/bfe/conf bfenetworks/bfe +``` + +## 下一步 +* 了解[命令行参数](../operation/command.md) +* 了解[基本功能配置使用](../example/guide.md)