op http client proxy

This commit is contained in:
beck-8
2025-06-19 22:52:51 +08:00
parent 2567652a7d
commit 8c127a795b
7 changed files with 396 additions and 426 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.idea
.vscode
.DS_Store
hubproxy*

View File

@@ -138,11 +138,17 @@ blackList = [
"baduser/*" "baduser/*"
] ]
# SOCKS5代理配置,支持有用户名/密码认证和无认证模式 # 代理配置,支持有用户名/密码认证和无认证模式
# 无认证: socks5://127.0.0.1:1080 # 无认证: socks5://127.0.0.1:1080
# 有认证: socks5://username:password@127.0.0.1:1080 # 有认证: socks5://username:password@127.0.0.1:1080
# HTTP 代理示例
# http://username:password@127.0.0.1:7890
# SOCKS5 代理示例
# socks5://username:password@127.0.0.1:1080
# SOCKS5H 代理示例
# socks5h://username:password@127.0.0.1:1080
# 留空不使用代理 # 留空不使用代理
socks5 = "" proxy = ""
[download] [download]
# 批量下载离线镜像数量限制 # 批量下载离线镜像数量限制

View File

@@ -32,7 +32,7 @@ var GlobalAccessController = &AccessController{}
// ParseDockerImage 解析Docker镜像名称 // ParseDockerImage 解析Docker镜像名称
func (ac *AccessController) ParseDockerImage(image string) DockerImageInfo { func (ac *AccessController) ParseDockerImage(image string) DockerImageInfo {
image = strings.TrimPrefix(image, "docker://") image = strings.TrimPrefix(image, "docker://")
var tag string var tag string
if idx := strings.LastIndex(image, ":"); idx != -1 { if idx := strings.LastIndex(image, ":"); idx != -1 {
part := image[idx+1:] part := image[idx+1:]
@@ -44,7 +44,7 @@ func (ac *AccessController) ParseDockerImage(image string) DockerImageInfo {
if tag == "" { if tag == "" {
tag = "latest" tag = "latest"
} }
var namespace, repository string var namespace, repository string
if strings.Contains(image, "/") { if strings.Contains(image, "/") {
parts := strings.Split(image, "/") parts := strings.Split(image, "/")
@@ -66,9 +66,9 @@ func (ac *AccessController) ParseDockerImage(image string) DockerImageInfo {
namespace = "library" namespace = "library"
repository = image repository = image
} }
fullName := namespace + "/" + repository fullName := namespace + "/" + repository
return DockerImageInfo{ return DockerImageInfo{
Namespace: namespace, Namespace: namespace,
Repository: repository, Repository: repository,
@@ -80,24 +80,24 @@ func (ac *AccessController) ParseDockerImage(image string) DockerImageInfo {
// CheckDockerAccess 检查Docker镜像访问权限 // CheckDockerAccess 检查Docker镜像访问权限
func (ac *AccessController) CheckDockerAccess(image string) (allowed bool, reason string) { func (ac *AccessController) CheckDockerAccess(image string) (allowed bool, reason string) {
cfg := GetConfig() cfg := GetConfig()
// 解析镜像名称 // 解析镜像名称
imageInfo := ac.ParseDockerImage(image) imageInfo := ac.ParseDockerImage(image)
// 检查白名单(如果配置了白名单,则只允许白名单中的镜像) // 检查白名单(如果配置了白名单,则只允许白名单中的镜像)
if len(cfg.Proxy.WhiteList) > 0 { if len(cfg.Access.WhiteList) > 0 {
if !ac.matchImageInList(imageInfo, cfg.Proxy.WhiteList) { if !ac.matchImageInList(imageInfo, cfg.Access.WhiteList) {
return false, "不在Docker镜像白名单内" return false, "不在Docker镜像白名单内"
} }
} }
// 检查黑名单 // 检查黑名单
if len(cfg.Proxy.BlackList) > 0 { if len(cfg.Access.BlackList) > 0 {
if ac.matchImageInList(imageInfo, cfg.Proxy.BlackList) { if ac.matchImageInList(imageInfo, cfg.Access.BlackList) {
return false, "Docker镜像在黑名单内" return false, "Docker镜像在黑名单内"
} }
} }
return true, "" return true, ""
} }
@@ -106,19 +106,19 @@ func (ac *AccessController) CheckGitHubAccess(matches []string) (allowed bool, r
if len(matches) < 2 { if len(matches) < 2 {
return false, "无效的GitHub仓库格式" return false, "无效的GitHub仓库格式"
} }
cfg := GetConfig() cfg := GetConfig()
// 检查白名单 // 检查白名单
if len(cfg.Proxy.WhiteList) > 0 && !ac.checkList(matches, cfg.Proxy.WhiteList) { if len(cfg.Access.WhiteList) > 0 && !ac.checkList(matches, cfg.Access.WhiteList) {
return false, "不在GitHub仓库白名单内" return false, "不在GitHub仓库白名单内"
} }
// 检查黑名单 // 检查黑名单
if len(cfg.Proxy.BlackList) > 0 && ac.checkList(matches, cfg.Proxy.BlackList) { if len(cfg.Access.BlackList) > 0 && ac.checkList(matches, cfg.Access.BlackList) {
return false, "GitHub仓库在黑名单内" return false, "GitHub仓库在黑名单内"
} }
return true, "" return true, ""
} }
@@ -126,28 +126,28 @@ func (ac *AccessController) CheckGitHubAccess(matches []string) (allowed bool, r
func (ac *AccessController) matchImageInList(imageInfo DockerImageInfo, list []string) bool { func (ac *AccessController) matchImageInList(imageInfo DockerImageInfo, list []string) bool {
fullName := strings.ToLower(imageInfo.FullName) fullName := strings.ToLower(imageInfo.FullName)
namespace := strings.ToLower(imageInfo.Namespace) namespace := strings.ToLower(imageInfo.Namespace)
for _, item := range list { for _, item := range list {
item = strings.ToLower(strings.TrimSpace(item)) item = strings.ToLower(strings.TrimSpace(item))
if item == "" { if item == "" {
continue continue
} }
if fullName == item { if fullName == item {
return true return true
} }
if item == namespace || item == namespace+"/*" { if item == namespace || item == namespace+"/*" {
return true return true
} }
if strings.HasSuffix(item, "*") { if strings.HasSuffix(item, "*") {
prefix := strings.TrimSuffix(item, "*") prefix := strings.TrimSuffix(item, "*")
if strings.HasPrefix(fullName, prefix) { if strings.HasPrefix(fullName, prefix) {
return true return true
} }
} }
if strings.HasPrefix(item, "*/") { if strings.HasPrefix(item, "*/") {
repoPattern := strings.TrimPrefix(item, "*/") repoPattern := strings.TrimPrefix(item, "*/")
if strings.HasSuffix(repoPattern, "*") { if strings.HasSuffix(repoPattern, "*") {
@@ -161,7 +161,7 @@ func (ac *AccessController) matchImageInList(imageInfo DockerImageInfo, list []s
} }
} }
} }
if strings.HasPrefix(fullName, item+"/") { if strings.HasPrefix(fullName, item+"/") {
return true return true
} }
@@ -174,27 +174,27 @@ func (ac *AccessController) checkList(matches, list []string) bool {
if len(matches) < 2 { if len(matches) < 2 {
return false return false
} }
username := strings.ToLower(strings.TrimSpace(matches[0])) username := strings.ToLower(strings.TrimSpace(matches[0]))
repoName := strings.ToLower(strings.TrimSpace(strings.TrimSuffix(matches[1], ".git"))) repoName := strings.ToLower(strings.TrimSpace(strings.TrimSuffix(matches[1], ".git")))
fullRepo := username + "/" + repoName fullRepo := username + "/" + repoName
for _, item := range list { for _, item := range list {
item = strings.ToLower(strings.TrimSpace(item)) item = strings.ToLower(strings.TrimSpace(item))
if item == "" { if item == "" {
continue continue
} }
// 支持多种匹配模式 // 支持多种匹配模式
if fullRepo == item { if fullRepo == item {
return true return true
} }
// 用户级匹配 // 用户级匹配
if item == username || item == username+"/*" { if item == username || item == username+"/*" {
return true return true
} }
// 前缀匹配(支持通配符) // 前缀匹配(支持通配符)
if strings.HasSuffix(item, "*") { if strings.HasSuffix(item, "*") {
prefix := strings.TrimSuffix(item, "*") prefix := strings.TrimSuffix(item, "*")
@@ -202,7 +202,7 @@ func (ac *AccessController) checkList(matches, list []string) bool {
return true return true
} }
} }
// 子仓库匹配(防止 user/repo 匹配到 user/repo-fork // 子仓库匹配(防止 user/repo 匹配到 user/repo-fork
if strings.HasPrefix(fullRepo, item+"/") { if strings.HasPrefix(fullRepo, item+"/") {
return true return true
@@ -210,5 +210,3 @@ func (ac *AccessController) checkList(matches, list []string) bool {
} }
return false return false
} }

View File

@@ -1,275 +1,276 @@
package main package main
import ( import (
"fmt" "fmt"
"os" "os"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
) )
// RegistryMapping Registry映射配置 // RegistryMapping Registry映射配置
type RegistryMapping struct { type RegistryMapping struct {
Upstream string `toml:"upstream"` // 上游Registry地址 Upstream string `toml:"upstream"` // 上游Registry地址
AuthHost string `toml:"authHost"` // 认证服务器地址 AuthHost string `toml:"authHost"` // 认证服务器地址
AuthType string `toml:"authType"` // 认证类型: docker/github/google/basic AuthType string `toml:"authType"` // 认证类型: docker/github/google/basic
Enabled bool `toml:"enabled"` // 是否启用 Enabled bool `toml:"enabled"` // 是否启用
} }
// AppConfig 应用配置结构体 // AppConfig 应用配置结构体
type AppConfig struct { type AppConfig struct {
Server struct { Server struct {
Host string `toml:"host"` // 监听地址 Host string `toml:"host"` // 监听地址
Port int `toml:"port"` // 监听端口 Port int `toml:"port"` // 监听端口
FileSize int64 `toml:"fileSize"` // 文件大小限制(字节) FileSize int64 `toml:"fileSize"` // 文件大小限制(字节)
} `toml:"server"` } `toml:"server"`
RateLimit struct { RateLimit struct {
RequestLimit int `toml:"requestLimit"` // 每小时请求限制 RequestLimit int `toml:"requestLimit"` // 每小时请求限制
PeriodHours float64 `toml:"periodHours"` // 限制周期(小时) PeriodHours float64 `toml:"periodHours"` // 限制周期(小时)
} `toml:"rateLimit"` } `toml:"rateLimit"`
Security struct { Security struct {
WhiteList []string `toml:"whiteList"` // 白名单IP/CIDR列表 WhiteList []string `toml:"whiteList"` // 白名单IP/CIDR列表
BlackList []string `toml:"blackList"` // 黑名单IP/CIDR列表 BlackList []string `toml:"blackList"` // 黑名单IP/CIDR列表
} `toml:"security"` } `toml:"security"`
Proxy struct { Access struct {
WhiteList []string `toml:"whiteList"` // 代理白名单(仓库级别) WhiteList []string `toml:"whiteList"` // 代理白名单(仓库级别)
BlackList []string `toml:"blackList"` // 代理黑名单(仓库级别) BlackList []string `toml:"blackList"` // 代理黑名单(仓库级别)
Socks5 string `toml:"socks5"` // SOCKS5代理地址: socks5://[user:pass@]host:port Proxy string `toml:"proxy"` // 代理地址: 支持 http/https/socks5/socks5h
} `toml:"proxy"` } `toml:"proxy"`
Download struct { Download struct {
MaxImages int `toml:"maxImages"` // 单次下载最大镜像数量限制 MaxImages int `toml:"maxImages"` // 单次下载最大镜像数量限制
} `toml:"download"` } `toml:"download"`
Registries map[string]RegistryMapping `toml:"registries"` Registries map[string]RegistryMapping `toml:"registries"`
TokenCache struct { TokenCache struct {
Enabled bool `toml:"enabled"` // 是否启用token缓存 Enabled bool `toml:"enabled"` // 是否启用token缓存
DefaultTTL string `toml:"defaultTTL"` // 默认缓存时间 DefaultTTL string `toml:"defaultTTL"` // 默认缓存时间
} `toml:"tokenCache"` } `toml:"tokenCache"`
} }
var ( var (
appConfig *AppConfig appConfig *AppConfig
appConfigLock sync.RWMutex appConfigLock sync.RWMutex
cachedConfig *AppConfig cachedConfig *AppConfig
configCacheTime time.Time configCacheTime time.Time
configCacheTTL = 5 * time.Second configCacheTTL = 5 * time.Second
configCacheMutex sync.RWMutex configCacheMutex sync.RWMutex
) )
// DefaultConfig 返回默认配置 // todo:Refactoring is needed
func DefaultConfig() *AppConfig { // DefaultConfig 返回默认配置
return &AppConfig{ func DefaultConfig() *AppConfig {
Server: struct { return &AppConfig{
Host string `toml:"host"` Server: struct {
Port int `toml:"port"` Host string `toml:"host"`
FileSize int64 `toml:"fileSize"` Port int `toml:"port"`
}{ FileSize int64 `toml:"fileSize"`
Host: "0.0.0.0", }{
Port: 5000, Host: "0.0.0.0",
FileSize: 2 * 1024 * 1024 * 1024, // 2GB Port: 5000,
}, FileSize: 2 * 1024 * 1024 * 1024, // 2GB
RateLimit: struct { },
RequestLimit int `toml:"requestLimit"` RateLimit: struct {
PeriodHours float64 `toml:"periodHours"` RequestLimit int `toml:"requestLimit"`
}{ PeriodHours float64 `toml:"periodHours"`
RequestLimit: 20, }{
PeriodHours: 1.0, RequestLimit: 20,
}, PeriodHours: 1.0,
Security: struct { },
WhiteList []string `toml:"whiteList"` Security: struct {
BlackList []string `toml:"blackList"` WhiteList []string `toml:"whiteList"`
}{ BlackList []string `toml:"blackList"`
WhiteList: []string{}, }{
BlackList: []string{}, WhiteList: []string{},
}, BlackList: []string{},
Proxy: struct { },
WhiteList []string `toml:"whiteList"` Access: struct {
BlackList []string `toml:"blackList"` WhiteList []string `toml:"whiteList"`
Socks5 string `toml:"socks5"` BlackList []string `toml:"blackList"`
}{ Proxy string `toml:"proxy"`
WhiteList: []string{}, }{
BlackList: []string{}, WhiteList: []string{},
Socks5: "", // 默认不使用代理 BlackList: []string{},
}, Proxy: "", // 默认不使用代理
Download: struct { },
MaxImages int `toml:"maxImages"` Download: struct {
}{ MaxImages int `toml:"maxImages"`
MaxImages: 10, // 默认值最多同时下载10个镜像 }{
}, MaxImages: 10, // 默认值最多同时下载10个镜像
Registries: map[string]RegistryMapping{ },
"ghcr.io": { Registries: map[string]RegistryMapping{
Upstream: "ghcr.io", "ghcr.io": {
AuthHost: "ghcr.io/token", Upstream: "ghcr.io",
AuthType: "github", AuthHost: "ghcr.io/token",
Enabled: true, AuthType: "github",
}, Enabled: true,
"gcr.io": { },
Upstream: "gcr.io", "gcr.io": {
AuthHost: "gcr.io/v2/token", Upstream: "gcr.io",
AuthType: "google", AuthHost: "gcr.io/v2/token",
Enabled: true, AuthType: "google",
}, Enabled: true,
"quay.io": { },
Upstream: "quay.io", "quay.io": {
AuthHost: "quay.io/v2/auth", Upstream: "quay.io",
AuthType: "quay", AuthHost: "quay.io/v2/auth",
Enabled: true, AuthType: "quay",
}, Enabled: true,
"registry.k8s.io": { },
Upstream: "registry.k8s.io", "registry.k8s.io": {
AuthHost: "registry.k8s.io", Upstream: "registry.k8s.io",
AuthType: "anonymous", AuthHost: "registry.k8s.io",
Enabled: true, AuthType: "anonymous",
}, Enabled: true,
}, },
TokenCache: struct { },
Enabled bool `toml:"enabled"` TokenCache: struct {
DefaultTTL string `toml:"defaultTTL"` Enabled bool `toml:"enabled"`
}{ DefaultTTL string `toml:"defaultTTL"`
Enabled: true, // docker认证的匿名Token缓存配置用于提升性能 }{
DefaultTTL: "20m", Enabled: true, // docker认证的匿名Token缓存配置用于提升性能
}, DefaultTTL: "20m",
} },
} }
}
// GetConfig 安全地获取配置副本
func GetConfig() *AppConfig { // GetConfig 安全地获取配置副本
configCacheMutex.RLock() func GetConfig() *AppConfig {
if cachedConfig != nil && time.Since(configCacheTime) < configCacheTTL { configCacheMutex.RLock()
config := cachedConfig if cachedConfig != nil && time.Since(configCacheTime) < configCacheTTL {
configCacheMutex.RUnlock() config := cachedConfig
return config configCacheMutex.RUnlock()
} return config
configCacheMutex.RUnlock() }
configCacheMutex.RUnlock()
// 缓存过期,重新生成配置
configCacheMutex.Lock() // 缓存过期,重新生成配置
defer configCacheMutex.Unlock() configCacheMutex.Lock()
defer configCacheMutex.Unlock()
// 双重检查,防止重复生成
if cachedConfig != nil && time.Since(configCacheTime) < configCacheTTL { // 双重检查,防止重复生成
return cachedConfig if cachedConfig != nil && time.Since(configCacheTime) < configCacheTTL {
} return cachedConfig
}
appConfigLock.RLock()
if appConfig == nil { appConfigLock.RLock()
appConfigLock.RUnlock() if appConfig == nil {
defaultCfg := DefaultConfig() appConfigLock.RUnlock()
cachedConfig = defaultCfg defaultCfg := DefaultConfig()
configCacheTime = time.Now() cachedConfig = defaultCfg
return defaultCfg configCacheTime = time.Now()
} return defaultCfg
}
// 生成新的配置深拷贝
configCopy := *appConfig // 生成新的配置深拷贝
configCopy.Security.WhiteList = append([]string(nil), appConfig.Security.WhiteList...) configCopy := *appConfig
configCopy.Security.BlackList = append([]string(nil), appConfig.Security.BlackList...) configCopy.Security.WhiteList = append([]string(nil), appConfig.Security.WhiteList...)
configCopy.Proxy.WhiteList = append([]string(nil), appConfig.Proxy.WhiteList...) configCopy.Security.BlackList = append([]string(nil), appConfig.Security.BlackList...)
configCopy.Proxy.BlackList = append([]string(nil), appConfig.Proxy.BlackList...) configCopy.Access.WhiteList = append([]string(nil), appConfig.Access.WhiteList...)
appConfigLock.RUnlock() configCopy.Access.BlackList = append([]string(nil), appConfig.Access.BlackList...)
appConfigLock.RUnlock()
cachedConfig = &configCopy
configCacheTime = time.Now() cachedConfig = &configCopy
configCacheTime = time.Now()
return cachedConfig
} return cachedConfig
}
// setConfig 安全地设置配置
func setConfig(cfg *AppConfig) { // setConfig 安全地设置配置
appConfigLock.Lock() func setConfig(cfg *AppConfig) {
defer appConfigLock.Unlock() appConfigLock.Lock()
appConfig = cfg defer appConfigLock.Unlock()
appConfig = cfg
configCacheMutex.Lock()
cachedConfig = nil configCacheMutex.Lock()
configCacheMutex.Unlock() cachedConfig = nil
} configCacheMutex.Unlock()
}
// LoadConfig 加载配置文件
func LoadConfig() error { // LoadConfig 加载配置文件
// 首先使用默认配置 func LoadConfig() error {
cfg := DefaultConfig() // 首先使用默认配置
cfg := DefaultConfig()
// 尝试加载TOML配置文件
if data, err := os.ReadFile("config.toml"); err == nil { // 尝试加载TOML配置文件
if err := toml.Unmarshal(data, cfg); err != nil { if data, err := os.ReadFile("config.toml"); err == nil {
return fmt.Errorf("解析配置文件失败: %v", err) if err := toml.Unmarshal(data, cfg); err != nil {
} return fmt.Errorf("解析配置文件失败: %v", err)
} else { }
fmt.Println("未找到config.toml使用默认配置") } else {
} fmt.Println("未找到config.toml使用默认配置")
}
// 从环境变量覆盖配置
overrideFromEnv(cfg) // 从环境变量覆盖配置
overrideFromEnv(cfg)
// 设置配置
setConfig(cfg) // 设置配置
setConfig(cfg)
return nil
} return nil
}
// overrideFromEnv 从环境变量覆盖配置
func overrideFromEnv(cfg *AppConfig) { // overrideFromEnv 从环境变量覆盖配置
// 服务器配置 func overrideFromEnv(cfg *AppConfig) {
if val := os.Getenv("SERVER_HOST"); val != "" { // 服务器配置
cfg.Server.Host = val if val := os.Getenv("SERVER_HOST"); val != "" {
} cfg.Server.Host = val
if val := os.Getenv("SERVER_PORT"); val != "" { }
if port, err := strconv.Atoi(val); err == nil && port > 0 { if val := os.Getenv("SERVER_PORT"); val != "" {
cfg.Server.Port = port if port, err := strconv.Atoi(val); err == nil && port > 0 {
} cfg.Server.Port = port
} }
if val := os.Getenv("MAX_FILE_SIZE"); val != "" { }
if size, err := strconv.ParseInt(val, 10, 64); err == nil && size > 0 { if val := os.Getenv("MAX_FILE_SIZE"); val != "" {
cfg.Server.FileSize = size if size, err := strconv.ParseInt(val, 10, 64); err == nil && size > 0 {
} cfg.Server.FileSize = size
} }
}
// 限流配置
if val := os.Getenv("RATE_LIMIT"); val != "" { // 限流配置
if limit, err := strconv.Atoi(val); err == nil && limit > 0 { if val := os.Getenv("RATE_LIMIT"); val != "" {
cfg.RateLimit.RequestLimit = limit if limit, err := strconv.Atoi(val); err == nil && limit > 0 {
} cfg.RateLimit.RequestLimit = limit
} }
if val := os.Getenv("RATE_PERIOD_HOURS"); val != "" { }
if period, err := strconv.ParseFloat(val, 64); err == nil && period > 0 { if val := os.Getenv("RATE_PERIOD_HOURS"); val != "" {
cfg.RateLimit.PeriodHours = period if period, err := strconv.ParseFloat(val, 64); err == nil && period > 0 {
} cfg.RateLimit.PeriodHours = period
} }
}
// IP限制配置
if val := os.Getenv("IP_WHITELIST"); val != "" { // IP限制配置
cfg.Security.WhiteList = append(cfg.Security.WhiteList, strings.Split(val, ",")...) if val := os.Getenv("IP_WHITELIST"); val != "" {
} cfg.Security.WhiteList = append(cfg.Security.WhiteList, strings.Split(val, ",")...)
if val := os.Getenv("IP_BLACKLIST"); val != "" { }
cfg.Security.BlackList = append(cfg.Security.BlackList, strings.Split(val, ",")...) if val := os.Getenv("IP_BLACKLIST"); val != "" {
} cfg.Security.BlackList = append(cfg.Security.BlackList, strings.Split(val, ",")...)
}
// 下载限制配置
if val := os.Getenv("MAX_IMAGES"); val != "" { // 下载限制配置
if maxImages, err := strconv.Atoi(val); err == nil && maxImages > 0 { if val := os.Getenv("MAX_IMAGES"); val != "" {
cfg.Download.MaxImages = maxImages if maxImages, err := strconv.Atoi(val); err == nil && maxImages > 0 {
} cfg.Download.MaxImages = maxImages
} }
} }
}
// CreateDefaultConfigFile 创建默认配置文件
func CreateDefaultConfigFile() error { // CreateDefaultConfigFile 创建默认配置文件
cfg := DefaultConfig() func CreateDefaultConfigFile() error {
cfg := DefaultConfig()
data, err := toml.Marshal(cfg)
if err != nil { data, err := toml.Marshal(cfg)
return fmt.Errorf("序列化默认配置失败: %v", err) if err != nil {
} return fmt.Errorf("序列化默认配置失败: %v", err)
}
return os.WriteFile("config.toml", data, 0644)
} return os.WriteFile("config.toml", data, 0644)
}

View File

@@ -26,7 +26,7 @@ blackList = [
"192.168.100.0/24" "192.168.100.0/24"
] ]
[proxy] [access]
# 代理服务白名单支持GitHub仓库和Docker镜像支持通配符 # 代理服务白名单支持GitHub仓库和Docker镜像支持通配符
# 只允许访问白名单中的仓库/镜像,为空时不限制 # 只允许访问白名单中的仓库/镜像,为空时不限制
whiteList = [] whiteList = []
@@ -39,11 +39,17 @@ blackList = [
"baduser/*" "baduser/*"
] ]
# SOCKS5代理配置,支持有用户名/密码认证和无认证模式 # 代理配置,支持有用户名/密码认证和无认证模式
# 无认证: socks5://127.0.0.1:1080 # 无认证: socks5://127.0.0.1:1080
# 有认证: socks5://username:password@127.0.0.1:1080 # 有认证: socks5://username:password@127.0.0.1:1080
# HTTP 代理示例
# http://username:password@127.0.0.1:7890
# SOCKS5 代理示例
# socks5://username:password@127.0.0.1:1080
# SOCKS5H 代理示例
# socks5h://username:password@127.0.0.1:1080
# 留空不使用代理 # 留空不使用代理
socks5 = "" proxy = ""
[download] [download]
# 批量下载离线镜像数量限制 # 批量下载离线镜像数量限制

View File

@@ -6,7 +6,6 @@ require (
github.com/gin-gonic/gin v1.10.0 github.com/gin-gonic/gin v1.10.0
github.com/google/go-containerregistry v0.20.5 github.com/google/go-containerregistry v0.20.5
github.com/pelletier/go-toml/v2 v2.2.3 github.com/pelletier/go-toml/v2 v2.2.3
golang.org/x/net v0.33.0
golang.org/x/time v0.11.0 golang.org/x/time v0.11.0
) )
@@ -44,6 +43,7 @@ require (
github.com/vbatts/tar-split v0.12.1 // indirect github.com/vbatts/tar-split v0.12.1 // indirect
golang.org/x/arch v0.8.0 // indirect golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.32.0 // indirect golang.org/x/crypto v0.32.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sync v0.14.0 // indirect golang.org/x/sync v0.14.0 // indirect
golang.org/x/sys v0.33.0 // indirect golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.21.0 // indirect golang.org/x/text v0.21.0 // indirect

View File

@@ -1,113 +1,68 @@
package main package main
import ( import (
"context" "net"
"log" "net/http"
"net" "os"
"net/http" "time"
"net/url" )
"time"
var (
"golang.org/x/net/proxy" // 全局HTTP客户端 - 用于代理请求(长超时)
) globalHTTPClient *http.Client
// 搜索HTTP客户端 - 用于API请求短超时
var ( searchHTTPClient *http.Client
// 全局HTTP客户端 - 用于代理请求(长超时) )
globalHTTPClient *http.Client
// 搜索HTTP客户端 - 用于API请求短超时 // initHTTPClients 初始化HTTP客户端
searchHTTPClient *http.Client func initHTTPClients() {
) cfg := GetConfig()
// initHTTPClients 初始化HTTP客户端 if p := cfg.Access.Proxy; p != "" {
func initHTTPClients() { os.Setenv("HTTP_PROXY", p)
cfg := GetConfig() os.Setenv("HTTPS_PROXY", p)
}
// 创建DialContext函数支持SOCKS5代理 // 代理客户端配置 - 适用于大文件传输
createDialContext := func(timeout time.Duration) func(ctx context.Context, network, addr string) (net.Conn, error) { globalHTTPClient = &http.Client{
if cfg.Proxy.Socks5 == "" { Transport: &http.Transport{
// 没有配置代理,使用直连 Proxy: http.ProxyFromEnvironment,
dialer := &net.Dialer{ DialContext: (&net.Dialer{
Timeout: timeout, Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second, KeepAlive: 30 * time.Second,
} }).DialContext,
return dialer.DialContext MaxIdleConns: 1000,
} MaxIdleConnsPerHost: 1000,
IdleConnTimeout: 90 * time.Second,
// 解析SOCKS5代理URL TLSHandshakeTimeout: 10 * time.Second,
proxyURL, err := url.Parse(cfg.Proxy.Socks5) ExpectContinueTimeout: 1 * time.Second,
if err != nil { ResponseHeaderTimeout: 300 * time.Second,
log.Printf("SOCKS5代理配置错误使用直连: %v", err) },
dialer := &net.Dialer{ }
Timeout: timeout,
KeepAlive: 30 * time.Second, // 搜索客户端配置 - 适用于API调用
} searchHTTPClient = &http.Client{
return dialer.DialContext Timeout: 10 * time.Second,
} Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
// 创建基础dialer DialContext: (&net.Dialer{
baseDialer := &net.Dialer{ Timeout: 5 * time.Second,
Timeout: timeout, KeepAlive: 30 * time.Second,
KeepAlive: 30 * time.Second, }).DialContext,
} MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
// 创建SOCKS5代理dialer IdleConnTimeout: 90 * time.Second,
var auth *proxy.Auth TLSHandshakeTimeout: 5 * time.Second,
if proxyURL.User != nil { DisableCompression: false,
if password, ok := proxyURL.User.Password(); ok { },
auth = &proxy.Auth{ }
User: proxyURL.User.Username(), }
Password: password,
} // GetGlobalHTTPClient 获取全局HTTP客户端用于代理
} func GetGlobalHTTPClient() *http.Client {
} return globalHTTPClient
}
socks5Dialer, err := proxy.SOCKS5("tcp", proxyURL.Host, auth, baseDialer)
if err != nil { // GetSearchHTTPClient 获取搜索HTTP客户端用于API调用
log.Printf("创建SOCKS5代理失败使用直连: %v", err) func GetSearchHTTPClient() *http.Client {
return baseDialer.DialContext return searchHTTPClient
} }
log.Printf("使用SOCKS5代理: %s", proxyURL.Host)
// 返回带上下文的dial函数
return func(ctx context.Context, network, addr string) (net.Conn, error) {
return socks5Dialer.Dial(network, addr)
}
}
// 代理客户端配置 - 适用于大文件传输
globalHTTPClient = &http.Client{
Transport: &http.Transport{
DialContext: createDialContext(30 * time.Second),
MaxIdleConns: 1000,
MaxIdleConnsPerHost: 1000,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
ResponseHeaderTimeout: 300 * time.Second,
},
}
// 搜索客户端配置 - 适用于API调用
searchHTTPClient = &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
DialContext: createDialContext(5 * time.Second),
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 5 * time.Second,
DisableCompression: false,
},
}
}
// GetGlobalHTTPClient 获取全局HTTP客户端用于代理
func GetGlobalHTTPClient() *http.Client {
return globalHTTPClient
}
// GetSearchHTTPClient 获取搜索HTTP客户端用于API调用
func GetSearchHTTPClient() *http.Client {
return searchHTTPClient
}