package rules import ( "encoding/json" "net/http" ) type ( /* Example: proxy.app1.rules: | - name: default do: | rewrite / /index.html serve /var/www/goaccess - name: ws on: | header Connection Upgrade header Upgrade websocket do: bypass proxy.app2.rules: | - name: default do: bypass - name: block POST and PUT on: method POST | method PUT do: error 403 Forbidden */ Rules []*Rule /* Rule is a rule for a reverse proxy. It do `Do` when `On` matches. A rule can have multiple lines of on. All lines of on must match, but each line can have multiple checks that one match means this line is matched. */ Rule struct { Name string `json:"name"` On RuleOn `json:"on"` Do Command `json:"do"` } ) // BuildHandler returns a http.HandlerFunc that implements the rules. // // if a bypass rule matches, // the request is passed to the upstream and no more rules are executed. // // if no rule matches, the default rule is executed // if no rule matches and default rule is not set, // the request is passed to the upstream. func (rules Rules) BuildHandler(caller string, up http.Handler) http.HandlerFunc { var defaultRule *Rule nonDefaultRules := make(Rules, 0, len(rules)) for i, rule := range rules { if rule.Name == "default" { defaultRule = rule nonDefaultRules = append(nonDefaultRules, rules[:i]...) nonDefaultRules = append(nonDefaultRules, rules[i+1:]...) break } } if len(rules) == 0 { if defaultRule.Do.isBypass() { return up.ServeHTTP } return func(w http.ResponseWriter, r *http.Request) { cache := NewCache() defer cache.Release() if defaultRule.Do.exec.Handle(cache, w, r) { up.ServeHTTP(w, r) } } } return func(w http.ResponseWriter, r *http.Request) { cache := NewCache() defer cache.Release() for _, rule := range nonDefaultRules { if rule.Check(cache, r) { if rule.Do.isBypass() { up.ServeHTTP(w, r) return } if !rule.Handle(cache, w, r) { return } } } // bypass or proceed if defaultRule.Do.isBypass() || defaultRule.Handle(cache, w, r) { up.ServeHTTP(w, r) } } } func (rules Rules) MarshalJSON() ([]byte, error) { names := make([]string, len(rules)) for i, rule := range rules { names[i] = rule.Name } return json.Marshal(names) } func (rule *Rule) String() string { return rule.Name } func (rule *Rule) Check(cached Cache, r *http.Request) bool { return rule.On.checker.Check(cached, r) } func (rule *Rule) Handle(cached Cache, w http.ResponseWriter, r *http.Request) (proceed bool) { proceed = rule.Do.exec.Handle(cached, w, r) return }