mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 13:47:15 +08:00
feat: 支持apache
This commit is contained in:
@@ -86,10 +86,10 @@ func parseProxyFile(filePath string) (*types.Proxy, error) {
|
||||
proxy.Host = matches[1]
|
||||
}
|
||||
|
||||
// 解析 SSLProxyEngine 和 ProxySSL* (SNI)
|
||||
// 解析 SSLProxyEngine 和 SNI
|
||||
if regexp.MustCompile(`SSLProxyEngine\s+On`).MatchString(contentStr) {
|
||||
// 尝试获取 SNI
|
||||
sniPattern := regexp.MustCompile(`ProxyPassMatch.*ssl:([^/\s]+)`)
|
||||
// 尝试从 # SNI: xxx 注释中获取 SNI
|
||||
sniPattern := regexp.MustCompile(`#\s*SNI:\s*(\S+)`)
|
||||
if sm := sniPattern.FindStringSubmatch(contentStr); sm != nil {
|
||||
proxy.SNI = sm[1]
|
||||
}
|
||||
@@ -106,9 +106,11 @@ func parseProxyFile(filePath string) (*types.Proxy, error) {
|
||||
}
|
||||
|
||||
// 解析 Substitute (响应内容替换)
|
||||
subPattern := regexp.MustCompile(`Substitute\s+"s/([^/]+)/([^/]*)/[gin]*"`)
|
||||
subMatches := subPattern.FindAllStringSubmatch(contentStr, -1)
|
||||
for _, sm := range subMatches {
|
||||
// 格式: Substitute "s|from|to|n" 或 Substitute "s/from/to/n"
|
||||
// 使用 | 作为分隔符以支持包含 / 的内容
|
||||
subPatternPipe := regexp.MustCompile(`Substitute\s+"s\|([^|]*)\|([^|]*)\|[gin]*"`)
|
||||
subMatchesPipe := subPatternPipe.FindAllStringSubmatch(contentStr, -1)
|
||||
for _, sm := range subMatchesPipe {
|
||||
proxy.Replaces[sm[1]] = sm[2]
|
||||
}
|
||||
|
||||
@@ -178,9 +180,6 @@ func generateProxyConfig(proxy types.Proxy) string {
|
||||
var sb strings.Builder
|
||||
|
||||
location := proxy.Location
|
||||
if location == "" {
|
||||
location = "/"
|
||||
}
|
||||
// 将 Nginx 风格的 location 转换为 Apache 格式
|
||||
// ^~ / -> /
|
||||
// ~ ^/api -> /api (正则匹配需要使用 ProxyPassMatch)
|
||||
@@ -194,6 +193,12 @@ func generateProxyConfig(proxy types.Proxy) string {
|
||||
location = "/" + location
|
||||
}
|
||||
|
||||
// Pass 不以 / 结尾时,添加 /
|
||||
// 垃圾 Apache 需要这样才不报错
|
||||
if !strings.HasSuffix(proxy.Pass, "/") {
|
||||
proxy.Pass += "/"
|
||||
}
|
||||
|
||||
sb.WriteString(fmt.Sprintf("# Reverse proxy: %s -> %s\n", location, proxy.Pass))
|
||||
|
||||
// 启用代理模块
|
||||
@@ -210,17 +215,16 @@ func generateProxyConfig(proxy types.Proxy) string {
|
||||
sb.WriteString(" ProxyPreserveHost On\n")
|
||||
}
|
||||
|
||||
// 标准代理头
|
||||
sb.WriteString(" RequestHeader set X-Real-IP \"%{REMOTE_ADDR}e\"\n")
|
||||
sb.WriteString(" RequestHeader set X-Forwarded-For \"%{X-Forwarded-For}e\"\n")
|
||||
sb.WriteString(" RequestHeader set X-Forwarded-Proto \"%{REQUEST_SCHEME}e\"\n")
|
||||
|
||||
// SSL/SNI 配置
|
||||
if proxy.SNI != "" || strings.HasPrefix(proxy.Pass, "https://") {
|
||||
sb.WriteString(" SSLProxyEngine On\n")
|
||||
sb.WriteString(" SSLProxyVerify none\n")
|
||||
sb.WriteString(" SSLProxyCheckPeerCN off\n")
|
||||
sb.WriteString(" SSLProxyCheckPeerName off\n")
|
||||
if proxy.SNI != "" {
|
||||
// 垃圾 Apache 不支持自定义 SNI,写这里只是备注一下
|
||||
sb.WriteString(fmt.Sprintf(" # SNI: %s\n", proxy.SNI))
|
||||
}
|
||||
}
|
||||
|
||||
// Buffering 配置
|
||||
@@ -241,7 +245,8 @@ func generateProxyConfig(proxy types.Proxy) string {
|
||||
sb.WriteString(" <IfModule mod_substitute.c>\n")
|
||||
sb.WriteString(" AddOutputFilterByType SUBSTITUTE text/html text/plain text/xml\n")
|
||||
for from, to := range proxy.Replaces {
|
||||
sb.WriteString(fmt.Sprintf(" Substitute \"s/%s/%s/n\"\n", from, to))
|
||||
// 使用 | 作为分隔符以支持包含 / 的内容
|
||||
sb.WriteString(fmt.Sprintf(" Substitute \"s|%s|%s|n\"\n", from, to))
|
||||
}
|
||||
sb.WriteString(" </IfModule>\n")
|
||||
}
|
||||
@@ -307,29 +312,25 @@ func parseBalancerFile(filePath string, name string) (*types.Upstream, error) {
|
||||
// </Proxy>
|
||||
|
||||
// 解析 BalancerMember
|
||||
memberPattern := regexp.MustCompile(`BalancerMember\s+(\S+)(?:\s+(.+))?`)
|
||||
memberMatches := memberPattern.FindAllStringSubmatch(contentStr, -1)
|
||||
for _, mm := range memberMatches {
|
||||
addr := mm[1]
|
||||
options := ""
|
||||
if len(mm) > 2 {
|
||||
options = strings.TrimSpace(mm[2])
|
||||
memberPattern := regexp.MustCompile(`^\s*BalancerMember\s+(\S+)(?:\s+(.*))?$`)
|
||||
lines := strings.Split(contentStr, "\n")
|
||||
for _, line := range lines {
|
||||
if mm := memberPattern.FindStringSubmatch(line); mm != nil {
|
||||
addr := mm[1]
|
||||
options := ""
|
||||
if len(mm) > 2 {
|
||||
options = strings.TrimSpace(mm[2])
|
||||
}
|
||||
upstream.Servers[addr] = options
|
||||
}
|
||||
upstream.Servers[addr] = options
|
||||
}
|
||||
|
||||
// 解析负载均衡方法
|
||||
lbMethodPattern := regexp.MustCompile(`lbmethod=(\S+)`)
|
||||
if lm := lbMethodPattern.FindStringSubmatch(contentStr); lm != nil {
|
||||
switch lm[1] {
|
||||
case "byrequests":
|
||||
upstream.Algo = ""
|
||||
case "bytraffic":
|
||||
upstream.Algo = "bytraffic"
|
||||
case "bybusyness":
|
||||
upstream.Algo = "least_conn"
|
||||
case "heartbeat":
|
||||
upstream.Algo = "heartbeat"
|
||||
// byrequests 是默认值,不需要存储
|
||||
if lm[1] != "byrequests" {
|
||||
upstream.Algo = lm[1]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,13 +410,8 @@ func generateBalancerConfig(upstream types.Upstream) string {
|
||||
|
||||
// 负载均衡方法
|
||||
lbMethod := "byrequests" // 默认轮询
|
||||
switch upstream.Algo {
|
||||
case "least_conn":
|
||||
lbMethod = "bybusyness"
|
||||
case "bytraffic":
|
||||
lbMethod = "bytraffic"
|
||||
case "heartbeat":
|
||||
lbMethod = "heartbeat"
|
||||
if upstream.Algo != "" {
|
||||
lbMethod = upstream.Algo
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf(" ProxySet lbmethod=%s\n", lbMethod))
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ func (v *baseVhost) SetListen(listens []types.Listen) error {
|
||||
}
|
||||
|
||||
func (v *baseVhost) ServerName() []string {
|
||||
var names []string
|
||||
names := make([]string, 0)
|
||||
|
||||
// 获取 ServerName
|
||||
serverName := v.vhost.GetDirectiveValue("ServerName")
|
||||
@@ -218,7 +218,7 @@ func (v *baseVhost) Index() []string {
|
||||
if values != nil {
|
||||
return values
|
||||
}
|
||||
return nil
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (v *baseVhost) SetIndex(index []string) error {
|
||||
|
||||
@@ -553,7 +553,7 @@ func (s *ProxyVhostTestSuite) TestUpstreams() {
|
||||
"http://127.0.0.1:8080": "loadfactor=5",
|
||||
"http://127.0.0.1:8081": "loadfactor=3",
|
||||
},
|
||||
Algo: "least_conn",
|
||||
Algo: "bybusyness",
|
||||
Keepalive: 32,
|
||||
},
|
||||
}
|
||||
@@ -578,7 +578,7 @@ func (s *ProxyVhostTestSuite) TestBalancerConfig() {
|
||||
Servers: map[string]string{
|
||||
"http://127.0.0.1:8080": "loadfactor=5",
|
||||
},
|
||||
Algo: "least_conn",
|
||||
Algo: "bybusyness",
|
||||
Keepalive: 16,
|
||||
},
|
||||
}
|
||||
@@ -596,7 +596,7 @@ func (s *ProxyVhostTestSuite) TestBalancerConfig() {
|
||||
s.Contains(string(content), "balancer://mybackend")
|
||||
s.Contains(string(content), "BalancerMember")
|
||||
s.Contains(string(content), "http://127.0.0.1:8080")
|
||||
s.Contains(string(content), "lbmethod=bybusyness") // least_conn 映射为 bybusyness
|
||||
s.Contains(string(content), "lbmethod=bybusyness")
|
||||
}
|
||||
|
||||
func (s *ProxyVhostTestSuite) TestClearUpstreams() {
|
||||
@@ -612,3 +612,113 @@ func (s *ProxyVhostTestSuite) TestClearUpstreams() {
|
||||
s.NoError(s.vhost.ClearUpstreams())
|
||||
s.Empty(s.vhost.Upstreams())
|
||||
}
|
||||
|
||||
func (s *ProxyVhostTestSuite) TestProxySNI() {
|
||||
// 测试 SNI 配置的写入和解析
|
||||
proxies := []types.Proxy{
|
||||
{
|
||||
Location: "/",
|
||||
Pass: "https://backend:443/",
|
||||
SNI: "backend.example.com",
|
||||
},
|
||||
}
|
||||
s.NoError(s.vhost.SetProxies(proxies))
|
||||
|
||||
// 读取配置文件内容,验证 SNI 已写入
|
||||
siteDir := filepath.Join(s.configDir, "site")
|
||||
content, err := os.ReadFile(filepath.Join(siteDir, "200-proxy.conf"))
|
||||
s.NoError(err)
|
||||
|
||||
s.Contains(string(content), "SSLProxyEngine On")
|
||||
s.Contains(string(content), "# SNI: backend.example.com")
|
||||
s.Contains(string(content), "RequestHeader set Host \"backend.example.com\"")
|
||||
|
||||
// 验证可以解析回来
|
||||
got := s.vhost.Proxies()
|
||||
s.Require().Len(got, 1)
|
||||
s.Equal("backend.example.com", got[0].SNI)
|
||||
}
|
||||
|
||||
func (s *ProxyVhostTestSuite) TestProxySubstitute() {
|
||||
// 测试内容替换的写入和解析
|
||||
proxies := []types.Proxy{
|
||||
{
|
||||
Location: "/",
|
||||
Pass: "http://backend:8080/",
|
||||
Replaces: map[string]string{
|
||||
"http://old.example.com": "https://new.example.com",
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
}
|
||||
s.NoError(s.vhost.SetProxies(proxies))
|
||||
|
||||
// 读取配置文件内容,验证 Substitute 已写入
|
||||
siteDir := filepath.Join(s.configDir, "site")
|
||||
content, err := os.ReadFile(filepath.Join(siteDir, "200-proxy.conf"))
|
||||
s.NoError(err)
|
||||
|
||||
s.Contains(string(content), "mod_substitute")
|
||||
s.Contains(string(content), "Substitute")
|
||||
|
||||
// 验证可以解析回来
|
||||
got := s.vhost.Proxies()
|
||||
s.Require().Len(got, 1)
|
||||
s.Require().NotNil(got[0].Replaces)
|
||||
s.Equal("https://new.example.com", got[0].Replaces["http://old.example.com"])
|
||||
s.Equal("bar", got[0].Replaces["foo"])
|
||||
}
|
||||
|
||||
func (s *ProxyVhostTestSuite) TestProxySubstituteWithSlash() {
|
||||
// 测试包含 / 的内容替换
|
||||
proxies := []types.Proxy{
|
||||
{
|
||||
Location: "/",
|
||||
Pass: "http://backend:8080/",
|
||||
Replaces: map[string]string{
|
||||
"http://old.example.com/path/to/resource": "https://new.example.com/new/path",
|
||||
},
|
||||
},
|
||||
}
|
||||
s.NoError(s.vhost.SetProxies(proxies))
|
||||
|
||||
// 读取配置文件内容
|
||||
siteDir := filepath.Join(s.configDir, "site")
|
||||
content, err := os.ReadFile(filepath.Join(siteDir, "200-proxy.conf"))
|
||||
s.NoError(err)
|
||||
|
||||
// 验证使用 | 作为分隔符
|
||||
s.Contains(string(content), "Substitute \"s|http://old.example.com/path/to/resource|https://new.example.com/new/path|n\"")
|
||||
|
||||
// 验证可以解析回来
|
||||
got := s.vhost.Proxies()
|
||||
s.Require().Len(got, 1)
|
||||
s.Require().NotNil(got[0].Replaces)
|
||||
s.Equal("https://new.example.com/new/path", got[0].Replaces["http://old.example.com/path/to/resource"])
|
||||
}
|
||||
|
||||
func (s *ProxyVhostTestSuite) TestUpstreamMultipleServers() {
|
||||
// 测试多个 BalancerMember 的解析
|
||||
upstreams := []types.Upstream{
|
||||
{
|
||||
Name: "test_upstream",
|
||||
Servers: map[string]string{
|
||||
"127.0.0.1:8080": "",
|
||||
"127.0.0.1:8081": "",
|
||||
"127.0.0.1:8082": "loadfactor=5",
|
||||
},
|
||||
Keepalive: 32,
|
||||
},
|
||||
}
|
||||
s.NoError(s.vhost.SetUpstreams(upstreams))
|
||||
|
||||
// 验证可以解析回来
|
||||
got := s.vhost.Upstreams()
|
||||
s.Require().Len(got, 1)
|
||||
s.Equal("test_upstream", got[0].Name)
|
||||
s.Len(got[0].Servers, 3)
|
||||
s.Contains(got[0].Servers, "127.0.0.1:8080")
|
||||
s.Contains(got[0].Servers, "127.0.0.1:8081")
|
||||
s.Contains(got[0].Servers, "127.0.0.1:8082")
|
||||
s.Equal("loadfactor=5", got[0].Servers["127.0.0.1:8082"])
|
||||
}
|
||||
|
||||
@@ -212,9 +212,6 @@ func generateProxyConfig(proxy types.Proxy) string {
|
||||
var sb strings.Builder
|
||||
|
||||
location := proxy.Location
|
||||
if location == "" {
|
||||
location = "/"
|
||||
}
|
||||
|
||||
sb.WriteString(fmt.Sprintf("location %s {\n", location))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user