144 lines
4.6 KiB
Go
144 lines
4.6 KiB
Go
/*
|
||
made&PR by @lfhy
|
||
https://github.com/WJQSERVER-STUDIO/ghproxy/pull/46
|
||
*/
|
||
|
||
package proxy
|
||
|
||
import (
|
||
"ghproxy/config"
|
||
"log"
|
||
"net/http"
|
||
"net/url"
|
||
"strings"
|
||
|
||
"golang.org/x/net/proxy"
|
||
)
|
||
|
||
// initTransport 初始化 HTTP 传输层的代理设置
|
||
func initTransport(cfg *config.Config, transport *http.Transport) {
|
||
// 如果代理功能未启用,直接返回
|
||
if !cfg.Outbound.Enabled {
|
||
return
|
||
}
|
||
|
||
// 如果代理 URL 未设置,使用环境变量中的代理配置
|
||
if cfg.Outbound.Url == "" {
|
||
transport.Proxy = http.ProxyFromEnvironment
|
||
//logWarning("Outbound proxy is not set, using environment variables")
|
||
log.Printf("Outbound proxy is not set, using environment variables")
|
||
return
|
||
}
|
||
|
||
// 尝试解析代理 URL
|
||
proxyInfo, err := url.Parse(cfg.Outbound.Url)
|
||
if err != nil {
|
||
// 如果解析失败,记录错误日志并使用环境变量中的代理配置
|
||
log.Printf("Failed to parse outbound proxy URL %v", err)
|
||
transport.Proxy = http.ProxyFromEnvironment
|
||
return
|
||
}
|
||
|
||
// 根据代理 URL 的 scheme(协议类型)选择代理类型
|
||
switch strings.ToLower(proxyInfo.Scheme) {
|
||
case "http", "https": // 如果是 HTTP/HTTPS 代理
|
||
transport.Proxy = http.ProxyURL(proxyInfo) // 设置 HTTP(S) 代理
|
||
log.Printf("Using HTTP(S) proxy: %s", cfg.Outbound.Url)
|
||
case "socks5": // 如果是 SOCKS5 代理
|
||
// 调用 newProxyDial 创建 SOCKS5 代理拨号器
|
||
proxyDialer := newProxyDial(cfg.Outbound.Url)
|
||
transport.Proxy = nil // 禁用 HTTP Proxy 设置,因为 SOCKS5 不需要 HTTP Proxy
|
||
|
||
// 尝试将 Dialer 转换为支持上下文的 ContextDialer
|
||
if contextDialer, ok := proxyDialer.(proxy.ContextDialer); ok {
|
||
transport.DialContext = contextDialer.DialContext
|
||
} else {
|
||
// 如果不支持 ContextDialer,则回退到传统的 Dial 方法
|
||
transport.Dial = proxyDialer.Dial
|
||
//logWarning("SOCKS5 dialer does not support ContextDialer, using legacy Dial")
|
||
log.Printf("SOCKS5 dialer does not support ContextDialer, using legacy Dial")
|
||
}
|
||
//logInfo("Using SOCKS5 proxy chain: %s", cfg.Outbound.Url)
|
||
log.Printf("Using SOCKS5 proxy chain: %s", cfg.Outbound.Url)
|
||
default: // 如果代理协议不支持
|
||
//logError("Unsupported proxy scheme: %s", proxyInfo.Scheme)
|
||
log.Printf("Unsupported proxy scheme: %s", proxyInfo.Scheme)
|
||
transport.Proxy = http.ProxyFromEnvironment // 回退到环境变量代理
|
||
}
|
||
}
|
||
|
||
// newProxyDial 创建一个 SOCKS5 代理拨号器
|
||
func newProxyDial(proxyUrls string) proxy.Dialer {
|
||
var proxyDialer proxy.Dialer = proxy.Direct // 初始为直接连接,不使用代理
|
||
|
||
// 支持多个代理 URL(以逗号分隔)
|
||
for _, proxyUrl := range strings.Split(proxyUrls, ",") {
|
||
proxyUrl = strings.TrimSpace(proxyUrl) // 去除首尾空格
|
||
if proxyUrl == "" { // 跳过空的代理 URL
|
||
continue
|
||
}
|
||
|
||
// 解析代理 URL
|
||
urlInfo, err := url.Parse(proxyUrl)
|
||
if err != nil {
|
||
// 如果 URL 解析失败,记录错误日志并跳过
|
||
//logError("Failed to parse proxy URL %q: %v", proxyUrl, err)
|
||
log.Printf("Failed to parse proxy URL %q: %v", proxyUrl, err)
|
||
continue
|
||
}
|
||
|
||
// 检查代理协议是否为 SOCKS5
|
||
if urlInfo.Scheme != "socks5" {
|
||
// logWarning("Skipping non-SOCKS5 proxy: %s", urlInfo.Scheme)
|
||
log.Printf("Skipping non-SOCKS5 proxy: %s", urlInfo.Scheme)
|
||
continue
|
||
}
|
||
|
||
// 解析代理认证信息(用户名和密码)
|
||
auth := parseAuth(urlInfo)
|
||
|
||
// 创建 SOCKS5 代理拨号器
|
||
dialer, err := createSocksDialer(urlInfo.Host, auth, proxyDialer)
|
||
if err != nil {
|
||
// 如果创建失败,记录错误日志并跳过
|
||
//logError("Failed to create SOCKS5 dialer for %q: %v", proxyUrl, err)
|
||
log.Printf("Failed to create SOCKS5 dialer for %q: %v", proxyUrl, err)
|
||
continue
|
||
}
|
||
|
||
// 更新代理拨号器,支持代理链
|
||
proxyDialer = dialer
|
||
}
|
||
|
||
return proxyDialer
|
||
}
|
||
|
||
// parseAuth 解析代理 URL 中的认证信息(用户名和密码)
|
||
func parseAuth(urlInfo *url.URL) *proxy.Auth {
|
||
// 如果 URL 中没有用户信息,返回 nil
|
||
if urlInfo.User == nil {
|
||
return nil
|
||
}
|
||
|
||
// 获取用户名
|
||
username := urlInfo.User.Username()
|
||
|
||
// 获取密码(注意:Password() 返回两个值,需要显式处理第二个值)
|
||
password, passwordSet := urlInfo.User.Password()
|
||
if !passwordSet {
|
||
password = "" // 如果密码未设置,使用空字符串
|
||
}
|
||
|
||
// 返回包含用户名和密码的认证信息
|
||
return &proxy.Auth{
|
||
User: username,
|
||
Password: password, // 允许空密码
|
||
}
|
||
}
|
||
|
||
// createSocksDialer 创建 SOCKS5 拨号器
|
||
func createSocksDialer(host string, auth *proxy.Auth, previous proxy.Dialer) (proxy.Dialer, error) {
|
||
// 调用 golang.org/x/net/proxy 提供的 SOCKS5 方法创建拨号器
|
||
return proxy.SOCKS5("tcp", host, auth, previous)
|
||
}
|