Files
-Fonu/internal/nginx/endpoints.go
T
mrlee fd1661622e feat: 上游 1.0.3-1.0.5 同步第 1-8 项(v1.2.2)
剪贴板:新增 web/src/utils/clipboard.ts,5 处调用点支持非 HTTPS 环境(对齐 1.0.4)

日志中心:新增「FRP 日志」Tab,解析 frpc 日志为 时间/级别/来源/消息(对齐 1.0.3)

通知:单通道密文解密失败不再拖垮整份配置(对齐 1.0.5);容器补 TZ + tzdata(对齐 1.0.4);Telegram 代理自动补 http:// 并校验 scheme(对齐 1.0.3)

反向代理:复制不再追加「-复制」(对齐 1.0.5);监听占用校验合并表单规则与手动 Nginx 配置两张表(迁移 024 + endpoints 解析器),冲突提示写明占用规则;改端口的并发冲突改为可读提示

FRP:frps 弹窗补「VPS 防火墙放行 TCP 远程端口」提示(对齐 1.0.3);反代配置弹窗补齐内容区内边距与来源说明

文档:新增 docs/upstream-sync-plan.md(逐版本对照 + 第 5 项详细整理);VERSION 与 CHANGELOG 记 v1.2.2
2026-09-23 17:46:21 +08:00

114 lines
2.9 KiB
Go

package nginx
import (
"strconv"
"strings"
"github.com/fonu/fonu/internal/proxy"
)
// ParseServerEndpoints 从 Nginx 配置文本里抽取各 server 块的 (server_name, listen 端口) 组合,
// 返回 proxy.Endpoint(与表单规则的端点同一类型),用于手动(custom)规则的监听占用校验。
//
// 采取保守策略:解析不出的写法(include 片段、正则 server_name、listen unix:、变量)一律忽略。
// 该校验只用于保存时提示,宁可漏报也不能因为解析失败而拦住保存。
func ParseServerEndpoints(content string) []proxy.Endpoint {
var out []proxy.Endpoint
seen := map[string]bool{}
var (
inServer bool
depth int
ports []int
names []string
)
flush := func() {
// 未写 listen 的 server 块按 Nginx 默认监听端口 80 处理
if len(ports) == 0 && len(names) > 0 {
ports = []int{80}
}
for _, port := range ports {
for _, name := range names {
key := name + ":" + strconv.Itoa(port)
if seen[key] {
continue
}
seen[key] = true
out = append(out, proxy.Endpoint{Hostname: name, Port: port})
}
}
inServer, depth, ports, names = false, 0, nil, nil
}
for _, raw := range strings.Split(content, "\n") {
line := stripInlineComment(raw)
if line == "" {
continue
}
opens := strings.Count(line, "{")
closes := strings.Count(line, "}")
if !inServer {
if opens > 0 && serverBlockOpens(line) {
inServer, depth = true, opens-closes
}
continue
}
switch {
case strings.HasPrefix(line, "listen"):
if port, ok := listenPort(line); ok {
ports = append(ports, port)
}
case strings.HasPrefix(line, "server_name"):
names = append(names, serverNames(line)...)
}
depth += opens - closes
if depth <= 0 {
flush()
}
}
return out
}
func serverBlockOpens(line string) bool {
fields := strings.Fields(line)
return len(fields) > 0 && fields[0] == "server" && strings.Contains(line, "{")
}
// listenPort 从 listen 指令取出端口号;unix socket 等无法判定的写法返回 false
func listenPort(line string) (int, bool) {
fields := strings.Fields(line)
if len(fields) < 2 {
return 0, false
}
addr := strings.TrimSuffix(fields[1], ";")
if strings.Contains(addr, "unix:") {
return 0, false
}
if idx := strings.LastIndex(addr, ":"); idx >= 0 {
addr = addr[idx+1:]
}
port, err := strconv.Atoi(addr)
if err != nil || port <= 0 || port > 65535 {
return 0, false
}
return port, true
}
// serverNames 从 server_name 指令取出字面量主机名;正则(~ 开头)与占位符(含 $)跳过
func serverNames(line string) []string {
fields := strings.Fields(line)
if len(fields) < 2 {
return nil
}
var names []string
for _, field := range fields[1:] {
name := strings.ToLower(strings.TrimSuffix(field, ";"))
if name == "" || name == "_" || strings.HasPrefix(name, "~") || strings.Contains(name, "$") {
continue
}
names = append(names, name)
}
return names
}