mirror of
https://github.com/WJQSERVER-STUDIO/ghproxy.git
synced 2026-02-03 00:01:10 +08:00
update outbound
This commit is contained in:
parent
4c5d288f03
commit
2b7fbd2a0d
8 changed files with 139 additions and 46 deletions
|
|
@ -1,5 +1,11 @@
|
||||||
# 更新日志
|
# 更新日志
|
||||||
|
|
||||||
|
25w13a
|
||||||
|
---
|
||||||
|
- PRE-RELEASE: 此版本是v2.2.0的预发布版本,请勿在生产环境中使用;
|
||||||
|
- ADD: 加入`Socks5`和`HTTP(S)`出站支持
|
||||||
|
- CHANGE: 配置新增`Outbound`配置块
|
||||||
|
|
||||||
2.1.0
|
2.1.0
|
||||||
---
|
---
|
||||||
- RELEASE: v2.1.0正式版发布;
|
- RELEASE: v2.1.0正式版发布;
|
||||||
|
|
|
||||||
|
|
@ -116,9 +116,9 @@ rateMethod = "total" # "ip" or "total" 速率限制方式
|
||||||
ratePerMinute = 180 # 每分钟限制请求数量
|
ratePerMinute = 180 # 每分钟限制请求数量
|
||||||
burst = 5 # 突发请求数量
|
burst = 5 # 突发请求数量
|
||||||
|
|
||||||
[proxy]
|
[outbound]
|
||||||
enabled = false # 是否使用代理连接
|
enabled = false # 是否使用自定义代理出站
|
||||||
url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890" 支持HTTP代理和SOCKS5代理 支持多级SOCKS5代理
|
url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890" 支持Socks5/HTTP(S)出站传输
|
||||||
```
|
```
|
||||||
|
|
||||||
### 黑名单配置
|
### 黑名单配置
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ type Config struct {
|
||||||
Blacklist BlacklistConfig
|
Blacklist BlacklistConfig
|
||||||
Whitelist WhitelistConfig
|
Whitelist WhitelistConfig
|
||||||
RateLimit RateLimitConfig
|
RateLimit RateLimitConfig
|
||||||
Proxy ProxyConfig
|
Outbound OutboundConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerConfig struct {
|
type ServerConfig struct {
|
||||||
|
|
@ -63,7 +63,12 @@ type RateLimitConfig struct {
|
||||||
Burst int `toml:"burst"`
|
Burst int `toml:"burst"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyConfig struct {
|
/*
|
||||||
|
[outbound]
|
||||||
|
enabled = false
|
||||||
|
url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890"
|
||||||
|
*/
|
||||||
|
type OutboundConfig struct {
|
||||||
Enabled bool `toml:"enabled"`
|
Enabled bool `toml:"enabled"`
|
||||||
Url string `toml:"url"`
|
Url string `toml:"url"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,6 @@ rateMethod = "total" # "ip" or "total"
|
||||||
ratePerMinute = 180
|
ratePerMinute = 180
|
||||||
burst = 5
|
burst = 5
|
||||||
|
|
||||||
[proxy]
|
[outbound]
|
||||||
enabled = false
|
enabled = false
|
||||||
url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890"
|
url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890"
|
||||||
|
|
@ -36,3 +36,7 @@ enabled = false
|
||||||
rateMethod = "total" # "ip" or "total"
|
rateMethod = "total" # "ip" or "total"
|
||||||
ratePerMinute = 180
|
ratePerMinute = 180
|
||||||
burst = 5
|
burst = 5
|
||||||
|
|
||||||
|
[outbound]
|
||||||
|
enabled = false
|
||||||
|
url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890"
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,9 @@ func initChunkedHTTPClient(cfg *config.Config) {
|
||||||
KeepAlive: 30 * time.Second,
|
KeepAlive: 30 * time.Second,
|
||||||
}).DialContext,
|
}).DialContext,
|
||||||
}
|
}
|
||||||
initTransport(cfg, ctr)
|
if cfg.Outbound.Enabled {
|
||||||
|
initTransport(cfg, ctr)
|
||||||
|
}
|
||||||
cclient = &http.Client{
|
cclient = &http.Client{
|
||||||
Transport: ctr,
|
Transport: ctr,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
150
proxy/dial.go
150
proxy/dial.go
|
|
@ -1,3 +1,8 @@
|
||||||
|
/*
|
||||||
|
made&PR by @lfhy
|
||||||
|
https://github.com/WJQSERVER-STUDIO/ghproxy/pull/46
|
||||||
|
*/
|
||||||
|
|
||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -9,53 +14,122 @@ import (
|
||||||
"golang.org/x/net/proxy"
|
"golang.org/x/net/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newProxyDial(proxyUrls string) proxy.Dialer {
|
// initTransport 初始化 HTTP 传输层的代理设置
|
||||||
var proxyDialer proxy.Dialer = proxy.Direct
|
|
||||||
for _, proxyUrl := range strings.Split(proxyUrls, ",") {
|
|
||||||
urlInfo, err := url.Parse(proxyUrl)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if urlInfo.Scheme != "socks5" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var auth *proxy.Auth = nil
|
|
||||||
if urlInfo.User != nil {
|
|
||||||
pwd, ok := urlInfo.User.Password()
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
auth = &proxy.Auth{
|
|
||||||
User: urlInfo.User.Username(),
|
|
||||||
Password: pwd,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dialer, err := proxy.SOCKS5("tcp", urlInfo.Host, auth, proxyDialer)
|
|
||||||
if err == nil {
|
|
||||||
proxyDialer = dialer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return proxyDialer
|
|
||||||
}
|
|
||||||
|
|
||||||
func initTransport(cfg *config.Config, transport *http.Transport) {
|
func initTransport(cfg *config.Config, transport *http.Transport) {
|
||||||
if !cfg.Proxy.Enabled {
|
// 如果代理功能未启用,直接返回
|
||||||
|
if !cfg.Outbound.Enabled {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if cfg.Proxy.Url == "" {
|
|
||||||
|
// 如果代理 URL 未设置,使用环境变量中的代理配置
|
||||||
|
if cfg.Outbound.Url == "" {
|
||||||
|
transport.Proxy = http.ProxyFromEnvironment
|
||||||
|
logWarning("Outbound proxy is not set, using environment variables")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试解析代理 URL
|
||||||
|
proxyInfo, err := url.Parse(cfg.Outbound.Url)
|
||||||
|
if err != nil {
|
||||||
|
// 如果解析失败,记录错误日志并使用环境变量中的代理配置
|
||||||
|
logError("Failed to parse outbound proxy URL %v", err)
|
||||||
transport.Proxy = http.ProxyFromEnvironment
|
transport.Proxy = http.ProxyFromEnvironment
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyInfo, err := url.Parse(cfg.Proxy.Url)
|
// 根据代理 URL 的 scheme(协议类型)选择代理类型
|
||||||
if err == nil {
|
switch strings.ToLower(proxyInfo.Scheme) {
|
||||||
if strings.HasPrefix(cfg.Proxy.Url, "http") {
|
case "http", "https": // 如果是 HTTP/HTTPS 代理
|
||||||
transport.Proxy = http.ProxyURL(proxyInfo)
|
transport.Proxy = http.ProxyURL(proxyInfo) // 设置 HTTP(S) 代理
|
||||||
|
logInfo("Using HTTP(S) proxy: %s", proxyInfo.Redacted())
|
||||||
|
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 {
|
} else {
|
||||||
proxyDialer := newProxyDial(cfg.Proxy.Url)
|
// 如果不支持 ContextDialer,则回退到传统的 Dial 方法
|
||||||
transport.Dial = proxyDialer.Dial
|
transport.Dial = proxyDialer.Dial
|
||||||
transport.DialContext = proxyDialer.(proxy.ContextDialer).DialContext
|
logWarning("SOCKS5 dialer does not support ContextDialer, using legacy Dial")
|
||||||
}
|
}
|
||||||
|
logInfo("Using SOCKS5 proxy chain: %s", cfg.Outbound.Url)
|
||||||
|
default: // 如果代理协议不支持
|
||||||
|
logError("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)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查代理协议是否为 SOCKS5
|
||||||
|
if urlInfo.Scheme != "socks5" {
|
||||||
|
logWarning("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)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,9 @@ func initGitHTTPClient(cfg *config.Config) {
|
||||||
MaxConnsPerHost: 30,
|
MaxConnsPerHost: 30,
|
||||||
IdleConnTimeout: 30 * time.Second,
|
IdleConnTimeout: 30 * time.Second,
|
||||||
}
|
}
|
||||||
initTransport(cfg, gtr)
|
if cfg.Outbound.Enabled {
|
||||||
|
initTransport(cfg, ctr)
|
||||||
|
}
|
||||||
gclient = &http.Client{
|
gclient = &http.Client{
|
||||||
Transport: gtr,
|
Transport: gtr,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue