mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-20 12:42:34 +02:00

* 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>
125 lines
2.7 KiB
Go
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
|
|
}
|