Files
MeBox/internal/service/proxy.go
T

116 lines
3.1 KiB
Go

package service
import (
"net/http"
"net/url"
"strings"
"time"
)
// ProxyFromEnvironmentOrSystem mirrors http.ProxyFromEnvironment, then falls
// back to the OS user proxy. On Windows this means the current user's Internet
// Settings proxy, e.g. the proxy configured by Clash/V2RayN/系统设置.
func ProxyFromEnvironmentOrSystem(req *http.Request) (*url.URL, error) {
if proxy, err := http.ProxyFromEnvironment(req); proxy != nil || err != nil {
return proxy, err
}
return systemProxyForRequest(req)
}
// NewExternalHTTPClient builds an HTTP client for third-party APIs. It uses
// environment proxies first, then the local OS proxy configuration.
func NewExternalHTTPClient(timeout time.Duration) *http.Client {
return &http.Client{
Timeout: timeout,
Transport: NewExternalTransport(),
}
}
// NewInternalHTTPClient builds an HTTP client for LAN / Docker-internal
// services such as downloaders and other local tools. These endpoints are
// usually 127.0.0.1, host.docker.internal, 172.17.0.1 or a NAS LAN IP; sending
// them through HTTP_PROXY/SOCKS proxies makes local WebUI logins hang or fail.
func NewInternalHTTPClient(timeout time.Duration) *http.Client {
return &http.Client{
Timeout: timeout,
Transport: NewInternalTransport(),
}
}
func NewExternalTransport() *http.Transport {
return &http.Transport{
Proxy: ProxyFromEnvironmentOrSystem,
ForceAttemptHTTP2: true,
MaxIdleConns: 32,
MaxIdleConnsPerHost: 8,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 15 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
}
func NewInternalTransport() *http.Transport {
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.Proxy = nil
return transport
}
func proxyURLFromProxyServer(proxyServer, requestScheme string) (*url.URL, error) {
proxyServer = strings.TrimSpace(proxyServer)
if proxyServer == "" {
return nil, nil
}
if !strings.Contains(proxyServer, "=") {
return normalizeProxyURL(proxyServer, "http")
}
entries := strings.Split(proxyServer, ";")
values := map[string]string{}
first := ""
for _, entry := range entries {
key, value, ok := strings.Cut(strings.TrimSpace(entry), "=")
if !ok {
continue
}
key = strings.ToLower(strings.TrimSpace(key))
value = strings.TrimSpace(value)
if key == "" || value == "" {
continue
}
if first == "" {
first = value
}
values[key] = value
}
if value := values[strings.ToLower(requestScheme)]; value != "" {
return normalizeProxyURL(value, strings.ToLower(requestScheme))
}
if value := values["http"]; value != "" {
return normalizeProxyURL(value, "http")
}
if value := values["https"]; value != "" {
return normalizeProxyURL(value, "http")
}
if value := values["socks"]; value != "" {
return normalizeProxyURL(value, "socks")
}
return normalizeProxyURL(first, "http")
}
func normalizeProxyURL(raw, proxyKind string) (*url.URL, error) {
raw = strings.TrimSpace(raw)
if raw == "" {
return nil, nil
}
if !strings.Contains(raw, "://") {
scheme := "http"
if strings.EqualFold(proxyKind, "socks") {
scheme = "socks5"
}
raw = scheme + "://" + raw
}
return url.Parse(raw)
}