mirror of
				https://github.com/TeaOSLab/EdgeCommon.git
				synced 2025-11-04 05:00:24 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			346 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			346 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package serverconfigs
 | 
						||
 | 
						||
import (
 | 
						||
	"context"
 | 
						||
	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
 | 
						||
	"regexp"
 | 
						||
	"strconv"
 | 
						||
	"strings"
 | 
						||
)
 | 
						||
 | 
						||
type HTTPLocationConfig struct {
 | 
						||
	Id              int64                          `yaml:"id" json:"id"`                           // ID
 | 
						||
	IsOn            bool                           `yaml:"isOn" json:"isOn"`                       // 是否启用
 | 
						||
	Pattern         string                         `yaml:"pattern" json:"pattern"`                 // 匹配规则 TODO 未来支持更多样的匹配规则
 | 
						||
	Name            string                         `yaml:"name" json:"name"`                       // 名称
 | 
						||
	Web             *HTTPWebConfig                 `yaml:"web" json:"web"`                         // Web配置
 | 
						||
	URLPrefix       string                         `yaml:"urlPrefix" json:"urlPrefix"`             // 实际的URL前缀,TODO 未来支持变量
 | 
						||
	Description     string                         `yaml:"description" json:"description"`         // 描述
 | 
						||
	ReverseProxyRef *ReverseProxyRef               `yaml:"reverseProxyRef" json:"reverseProxyRef"` // 反向代理引用
 | 
						||
	ReverseProxy    *ReverseProxyConfig            `yaml:"reverseProxy" json:"reverseProxy"`       // 反向代理设置
 | 
						||
	IsBreak         bool                           `yaml:"isBreak" json:"isBreak"`                 // 终止向下解析
 | 
						||
	Children        []*HTTPLocationConfig          `yaml:"children" json:"children"`               // 子规则
 | 
						||
	Conds           *shared.HTTPRequestCondsConfig `yaml:"conds" json:"conds"`                     // 匹配条件
 | 
						||
	Domains         []string                       `yaml:"domains" json:"domains"`                 // 所属域名
 | 
						||
 | 
						||
	patternType HTTPLocationPatternType // 规则类型:LocationPattern*
 | 
						||
	prefix      string                  // 前缀
 | 
						||
	suffix      string                  // 后缀
 | 
						||
	path        string                  // 精确的路径
 | 
						||
 | 
						||
	reg             *regexp.Regexp // 匹配规则
 | 
						||
	caseInsensitive bool           // 大小写不敏感
 | 
						||
	reverse         bool           // 是否翻转规则,比如非前缀,非路径
 | 
						||
}
 | 
						||
 | 
						||
func (this *HTTPLocationConfig) Init(ctx context.Context) error {
 | 
						||
	err := this.ExtractPattern()
 | 
						||
	if err != nil {
 | 
						||
		return err
 | 
						||
	}
 | 
						||
 | 
						||
	if this.Web != nil {
 | 
						||
		err := this.Web.Init(ctx)
 | 
						||
		if err != nil {
 | 
						||
			return err
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	if this.ReverseProxyRef != nil {
 | 
						||
		err := this.ReverseProxyRef.Init()
 | 
						||
		if err != nil {
 | 
						||
			return err
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	if this.ReverseProxy != nil {
 | 
						||
		err := this.ReverseProxy.Init(ctx)
 | 
						||
		if err != nil {
 | 
						||
			return err
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	// Children
 | 
						||
	for _, child := range this.Children {
 | 
						||
		err := child.Init(ctx)
 | 
						||
		if err != nil {
 | 
						||
			return err
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	// conds
 | 
						||
	if this.Conds != nil {
 | 
						||
		err := this.Conds.Init()
 | 
						||
		if err != nil {
 | 
						||
			return err
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	return nil
 | 
						||
}
 | 
						||
 | 
						||
// SetPattern 组合参数为一个字符串
 | 
						||
func (this *HTTPLocationConfig) SetPattern(pattern string, patternType int, caseInsensitive bool, reverse bool) {
 | 
						||
	op := ""
 | 
						||
	if patternType == HTTPLocationPatternTypePrefix {
 | 
						||
		if caseInsensitive {
 | 
						||
			op = "*"
 | 
						||
			if reverse {
 | 
						||
				op = "!*"
 | 
						||
			}
 | 
						||
		} else {
 | 
						||
			if reverse {
 | 
						||
				op = "!"
 | 
						||
			}
 | 
						||
		}
 | 
						||
	} else if patternType == HTTPLocationPatternTypeSuffix {
 | 
						||
		op = "suffix"
 | 
						||
		if caseInsensitive {
 | 
						||
			op += "*"
 | 
						||
		}
 | 
						||
		if reverse {
 | 
						||
			op = "!" + op
 | 
						||
		}
 | 
						||
	} else if patternType == HTTPLocationPatternTypeExact {
 | 
						||
		op = "="
 | 
						||
		if caseInsensitive {
 | 
						||
			op += "*"
 | 
						||
		}
 | 
						||
		if reverse {
 | 
						||
			op = "!" + op
 | 
						||
		}
 | 
						||
	} else if patternType == HTTPLocationPatternTypeRegexp {
 | 
						||
		op = "~"
 | 
						||
		if caseInsensitive {
 | 
						||
			op += "*"
 | 
						||
		}
 | 
						||
		if reverse {
 | 
						||
			op = "!" + op
 | 
						||
		}
 | 
						||
	}
 | 
						||
	if len(op) > 0 {
 | 
						||
		pattern = op + " " + pattern
 | 
						||
	}
 | 
						||
	this.Pattern = pattern
 | 
						||
}
 | 
						||
 | 
						||
// PatternType 模式类型
 | 
						||
func (this *HTTPLocationConfig) PatternType() int {
 | 
						||
	return this.patternType
 | 
						||
}
 | 
						||
 | 
						||
// PatternString 模式字符串
 | 
						||
// 去掉了模式字符
 | 
						||
func (this *HTTPLocationConfig) PatternString() string {
 | 
						||
	if this.patternType == HTTPLocationPatternTypePrefix {
 | 
						||
		return this.prefix
 | 
						||
	}
 | 
						||
	if this.patternType == HTTPLocationPatternTypeSuffix {
 | 
						||
		return this.suffix
 | 
						||
	}
 | 
						||
	return this.path
 | 
						||
}
 | 
						||
 | 
						||
// IsReverse 是否翻转
 | 
						||
func (this *HTTPLocationConfig) IsReverse() bool {
 | 
						||
	return this.reverse
 | 
						||
}
 | 
						||
 | 
						||
// IsCaseInsensitive 是否大小写非敏感
 | 
						||
func (this *HTTPLocationConfig) IsCaseInsensitive() bool {
 | 
						||
	return this.caseInsensitive
 | 
						||
}
 | 
						||
 | 
						||
// ExtractPattern 分析匹配条件
 | 
						||
func (this *HTTPLocationConfig) ExtractPattern() error {
 | 
						||
	// 分析pattern
 | 
						||
	this.reverse = false
 | 
						||
	this.caseInsensitive = false
 | 
						||
	if len(this.Pattern) > 0 {
 | 
						||
		spaceIndex := strings.Index(this.Pattern, " ")
 | 
						||
		if spaceIndex < 0 {
 | 
						||
			this.patternType = HTTPLocationPatternTypePrefix
 | 
						||
			this.prefix = this.Pattern
 | 
						||
		} else {
 | 
						||
			cmd := this.Pattern[:spaceIndex]
 | 
						||
			pattern := strings.TrimSpace(this.Pattern[spaceIndex+1:])
 | 
						||
			if cmd == "*" { // 大小写非敏感
 | 
						||
				this.patternType = HTTPLocationPatternTypePrefix
 | 
						||
				this.prefix = pattern
 | 
						||
				this.caseInsensitive = true
 | 
						||
			} else if cmd == "!*" { // 大小写非敏感,翻转
 | 
						||
				this.patternType = HTTPLocationPatternTypePrefix
 | 
						||
				this.prefix = pattern
 | 
						||
				this.caseInsensitive = true
 | 
						||
				this.reverse = true
 | 
						||
			} else if cmd == "!" {
 | 
						||
				this.patternType = HTTPLocationPatternTypePrefix
 | 
						||
				this.prefix = pattern
 | 
						||
				this.reverse = true
 | 
						||
			} else if cmd == "=" {
 | 
						||
				this.patternType = HTTPLocationPatternTypeExact
 | 
						||
				this.path = pattern
 | 
						||
			} else if cmd == "=*" {
 | 
						||
				this.patternType = HTTPLocationPatternTypeExact
 | 
						||
				this.path = pattern
 | 
						||
				this.caseInsensitive = true
 | 
						||
			} else if cmd == "!=" {
 | 
						||
				this.patternType = HTTPLocationPatternTypeExact
 | 
						||
				this.path = pattern
 | 
						||
				this.reverse = true
 | 
						||
			} else if cmd == "!=*" {
 | 
						||
				this.patternType = HTTPLocationPatternTypeExact
 | 
						||
				this.path = pattern
 | 
						||
				this.reverse = true
 | 
						||
				this.caseInsensitive = true
 | 
						||
			} else if cmd == "~" { // 正则
 | 
						||
				this.patternType = HTTPLocationPatternTypeRegexp
 | 
						||
				reg, err := regexp.Compile(pattern)
 | 
						||
				if err != nil {
 | 
						||
					return err
 | 
						||
				}
 | 
						||
				this.reg = reg
 | 
						||
				this.path = pattern
 | 
						||
			} else if cmd == "!~" {
 | 
						||
				this.patternType = HTTPLocationPatternTypeRegexp
 | 
						||
				reg, err := regexp.Compile(pattern)
 | 
						||
				if err != nil {
 | 
						||
					return err
 | 
						||
				}
 | 
						||
				this.reg = reg
 | 
						||
				this.reverse = true
 | 
						||
				this.path = pattern
 | 
						||
			} else if cmd == "~*" { // 大小写非敏感小写
 | 
						||
				this.patternType = HTTPLocationPatternTypeRegexp
 | 
						||
				reg, err := regexp.Compile("(?i)" + pattern)
 | 
						||
				if err != nil {
 | 
						||
					return err
 | 
						||
				}
 | 
						||
				this.reg = reg
 | 
						||
				this.caseInsensitive = true
 | 
						||
				this.path = pattern
 | 
						||
			} else if cmd == "!~*" {
 | 
						||
				this.patternType = HTTPLocationPatternTypeRegexp
 | 
						||
				reg, err := regexp.Compile("(?i)" + pattern)
 | 
						||
				if err != nil {
 | 
						||
					return err
 | 
						||
				}
 | 
						||
				this.reg = reg
 | 
						||
				this.reverse = true
 | 
						||
				this.caseInsensitive = true
 | 
						||
				this.path = pattern
 | 
						||
			} else if cmd == "suffix" {
 | 
						||
				this.patternType = HTTPLocationPatternTypeSuffix
 | 
						||
				this.suffix = pattern
 | 
						||
			} else if cmd == "suffix*" {
 | 
						||
				this.patternType = HTTPLocationPatternTypeSuffix
 | 
						||
				this.caseInsensitive = true
 | 
						||
				this.suffix = pattern
 | 
						||
			} else if cmd == "!suffix" {
 | 
						||
				this.patternType = HTTPLocationPatternTypeSuffix
 | 
						||
				this.reverse = true
 | 
						||
				this.suffix = pattern
 | 
						||
			} else if cmd == "!suffix*" {
 | 
						||
				this.patternType = HTTPLocationPatternTypeSuffix
 | 
						||
				this.caseInsensitive = true
 | 
						||
				this.reverse = true
 | 
						||
				this.suffix = pattern
 | 
						||
			} else {
 | 
						||
				this.patternType = HTTPLocationPatternTypePrefix
 | 
						||
				this.prefix = pattern
 | 
						||
			}
 | 
						||
		}
 | 
						||
	} else {
 | 
						||
		this.patternType = HTTPLocationPatternTypePrefix
 | 
						||
		this.prefix = this.Pattern
 | 
						||
	}
 | 
						||
 | 
						||
	return nil
 | 
						||
}
 | 
						||
 | 
						||
// Match 判断是否匹配路径
 | 
						||
// TODO 支持子Location
 | 
						||
func (this *HTTPLocationConfig) Match(path string, formatter func(source string) string) (vars map[string]string, isMatched bool) {
 | 
						||
	// 判断条件
 | 
						||
	if this.Conds != nil && this.Conds.HasRequestConds() && !this.Conds.MatchRequest(formatter) {
 | 
						||
		return
 | 
						||
	}
 | 
						||
 | 
						||
	if this.patternType == HTTPLocationPatternTypePrefix {
 | 
						||
		if this.reverse {
 | 
						||
			if this.caseInsensitive {
 | 
						||
				return nil, !strings.HasPrefix(strings.ToLower(path), strings.ToLower(this.prefix))
 | 
						||
			} else {
 | 
						||
				return nil, !strings.HasPrefix(path, this.prefix)
 | 
						||
			}
 | 
						||
		} else {
 | 
						||
			if this.caseInsensitive {
 | 
						||
				return nil, strings.HasPrefix(strings.ToLower(path), strings.ToLower(this.prefix))
 | 
						||
			} else {
 | 
						||
				return nil, strings.HasPrefix(path, this.prefix)
 | 
						||
			}
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	if this.patternType == HTTPLocationPatternTypeSuffix {
 | 
						||
		if this.reverse {
 | 
						||
			if this.caseInsensitive {
 | 
						||
				return nil, !strings.HasSuffix(strings.ToLower(path), strings.ToLower(this.suffix))
 | 
						||
			} else {
 | 
						||
				return nil, !strings.HasSuffix(path, this.suffix)
 | 
						||
			}
 | 
						||
		} else {
 | 
						||
			if this.caseInsensitive {
 | 
						||
				return nil, strings.HasSuffix(strings.ToLower(path), strings.ToLower(this.suffix))
 | 
						||
			} else {
 | 
						||
				return nil, strings.HasSuffix(path, this.suffix)
 | 
						||
			}
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	if this.patternType == HTTPLocationPatternTypeExact {
 | 
						||
		if this.reverse {
 | 
						||
			if this.caseInsensitive {
 | 
						||
				return nil, !strings.EqualFold(path, this.path)
 | 
						||
			} else {
 | 
						||
				return nil, path != this.path
 | 
						||
			}
 | 
						||
		} else {
 | 
						||
			if this.caseInsensitive {
 | 
						||
				return nil, strings.EqualFold(path, this.path)
 | 
						||
			} else {
 | 
						||
				return nil, path == this.path
 | 
						||
			}
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	// TODO 正则表达式匹配会让请求延迟0.01-0.02ms,可以使用缓存加速正则匹配,因为大部分路径都是不变的
 | 
						||
	if this.patternType == HTTPLocationPatternTypeRegexp {
 | 
						||
		if this.reg != nil {
 | 
						||
			if this.reverse {
 | 
						||
				return nil, !this.reg.MatchString(path)
 | 
						||
			} else {
 | 
						||
				b := this.reg.MatchString(path)
 | 
						||
				if b {
 | 
						||
					result := map[string]string{}
 | 
						||
					matches := this.reg.FindStringSubmatch(path)
 | 
						||
					subNames := this.reg.SubexpNames()
 | 
						||
					for index, value := range matches {
 | 
						||
						result[strconv.Itoa(index)] = value
 | 
						||
						subName := subNames[index]
 | 
						||
						if len(subName) > 0 {
 | 
						||
							result[subName] = value
 | 
						||
						}
 | 
						||
					}
 | 
						||
					return result, true
 | 
						||
				}
 | 
						||
				return nil, b
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		return nil, this.reverse
 | 
						||
	}
 | 
						||
 | 
						||
	return nil, false
 | 
						||
}
 |