GoDoxy/internal/route/rules/rules.go
Yuzerion 80bc018a7f
feat: custom json marshaling implementation, replace json and yaml library (#89)
* chore: replace gopkg.in/yaml.v3 vs goccy/go-yaml; replace encoding/json with bytedance/sonic

* fix: yaml unmarshal panic

* feat: custom json marshaler implementation

* chore: fix import and err marshal handling

---------

Co-authored-by: yusing <yusing@6uo.me>
2025-04-16 15:02:11 +08:00

125 lines
2.7 KiB
Go

package rules
import (
"net/http"
"github.com/yusing/go-proxy/pkg/json"
)
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,string"`
Do Command `json:"do,string"`
}
)
// 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) MarshalJSONTo(buf []byte) []byte {
names := make([]string, len(rules))
for i, rule := range rules {
names[i] = rule.Name
}
return json.MarshalTo(names, buf)
}
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
}