Compare commits

..

No commits in common. "master" and "v2.3.0" have entirely different histories.

8 changed files with 133 additions and 307 deletions

View File

@ -2,52 +2,36 @@
_A round-robin load balancer for HTTP proxies._ _A round-robin load balancer for HTTP proxies._
This is a simple proxy load balancer that will route requests to a cluster of proxy backends in a round-robin fashion. This is a simple proxy load balancer that will route requests to a cluster of proxy backends in a round-robin fashion. This makes it easy to connect your clients to a large number of proxy servers without worrying about implementing anything special clientside.
This makes it easy to connect your clients to a large number of proxy servers without worrying about implementing
anything special client-side.
This proxy server will transparently forward HTTPS requests without terminating them, meaning a self-signed certificate This proxy server will transparently forward HTTPS requests without terminating them, meaning a self-signed certificate is not required. Downstream HTTPS proxy servers are not supported.
is not required. Downstream HTTPS proxy servers are not supported.
Memory usage sits at around 25M under load. Memory usage sits at around 25M under load.
## Install ## Install
1. Download the latest release from [/releases](https://git.evulid.cc/cyberes/proxy-loadbalancer/releases) or 1. Download the latest release from [/releases](https://git.evulid.cc/cyberes/proxy-loadbalancer/releases) or run `./build.sh` to build the program locally.
run `./build.sh` to build the program locally.
2. `cp config.example.yml config.yml` 2. `cp config.example.yml config.yml`
3. Edit the config. 3. Edit the config.
4. Start the loadbalancer with `./proxy-loadbalancer --config [path to your config.yml]` 4. Start the loadbalancer with `./proxy-loadbalancer --config [path to your config.yml]`
The load balancer has experimental support for using [curl-impersonate](https://github.com/lwthiker/curl-impersonate) to masquerade as the Chrome browser when
performing proxy checks.
1. Download `*.x86_64-linux-gnu.tar.gz ` from https://github.com/lwthiker/curl-impersonate/releases
2. Set `proxy_check_impersonate_chrome: true`
3. Enter the path to the `curl_chrome116` binary in `proxy_check_impersonate_chrome_binary`
## Use ## Use
You can run your own "public IP delivery server" `canihazip` <https://git.evulid.cc/cyberes/canihazip> or use the You can run your own "public IP delivery server" `canihazip` <https://git.evulid.cc/cyberes/canihazip> or use the default `api.ipify.org`
default `api.ipify.org`
An example systemd service `loadbalancer.service` is provided. An example systemd service `loadbalancer.service` is provided.
The server displays health, stats, info at `/json`. The server displays health, stats, info at `/json`.
Use `--log-third-party-test-failures` along with `--debug` when you want extra info on the third-party proxy tests. This
can get very noisy if you have lots of third-party proxies so it's hidden behind an extra flag.
``` ```
=== Proxy Load Balancer === === Proxy Load Balancer ===
Usage of /tmp/go-build1714785557/b001/exe/proxy-loadbalancer: Usage of ./proxy-loadbalancer:
--config [string] --config [string]
Path to the config file Path to the config file
-d, --debug -d, --debug
Enable debug mode Enable debug mode
-l, --log-third-party-test-failures --v Print version and exit
Log third-party test debug info -h, --help Print this help message
-v Print version and exit
``` ```
## Special Headers ## Special Headers
@ -55,5 +39,4 @@ Usage of /tmp/go-build1714785557/b001/exe/proxy-loadbalancer:
The load balancer accepts special headers to control its behavior: The load balancer accepts special headers to control its behavior:
- `Thirdparty-Bypass`: don't use any third-party endpoints for this request. - `Thirdparty-Bypass`: don't use any third-party endpoints for this request.
- `Thirdparty-Include-Broken`: use all online endpoints for this request, including third-party ones that failed the - `Thirdparty-Include-Broken`: use all online endpoints for this request, including third-party ones that failed the special test.
special test.

View File

@ -13,14 +13,6 @@ ip_checker_url: https://api.ipify.org
# Connection timeout for the proxies in seconds. # Connection timeout for the proxies in seconds.
proxy_connect_timeout: 60 proxy_connect_timeout: 60
# How many times to retry a proxy connection.
# On each retry a new proxy will be chosen.
proxy_connect_retries: 3
# Use `curl-impersonate` to pretend to be Chrome when testing proxies.
proxy_check_impersonate_chrome: false
proxy_check_impersonate_chrome_binary: ./curl_chrome116
# Your proxies. # Your proxies.
proxy_pool_ours: proxy_pool_ours:
- http://1.2.3.4:3128 - http://1.2.3.4:3128

View File

@ -2,10 +2,7 @@ package config
import ( import (
"errors" "errors"
"fmt"
"github.com/spf13/viper" "github.com/spf13/viper"
"os"
"os/exec"
"time" "time"
) )
@ -25,9 +22,6 @@ type Config struct {
BlockedDomains []string BlockedDomains []string
ResolveThrough map[string]string ResolveThrough map[string]string
ProxyCheckInterval int ProxyCheckInterval int
ProxyCheckImpersonateChrome bool
ProxyCheckImpersonateChromeBinary string
ProxyConnectRetries int
} }
func SetConfig(configFile string) (*Config, error) { func SetConfig(configFile string) (*Config, error) {
@ -48,9 +42,6 @@ func SetConfig(configFile string) (*Config, error) {
viper.SetDefault("blocked_domains", make([]string, 0)) viper.SetDefault("blocked_domains", make([]string, 0))
viper.SetDefault("resolve_through", make(map[string]string)) viper.SetDefault("resolve_through", make(map[string]string))
viper.SetDefault("proxy_check_interval", 60) viper.SetDefault("proxy_check_interval", 60)
viper.SetDefault("proxy_check_impersonate_chrome", false)
viper.SetDefault("proxy_check_impersonate_chrome_binary", nil)
viper.SetDefault("proxy_connect_retries", 3)
err := viper.ReadInConfig() err := viper.ReadInConfig()
if err != nil { if err != nil {
@ -69,9 +60,6 @@ func SetConfig(configFile string) (*Config, error) {
BlockedDomains: viper.GetStringSlice("blocked_domains"), BlockedDomains: viper.GetStringSlice("blocked_domains"),
ResolveThrough: viper.GetStringMapString("resolve_through"), ResolveThrough: viper.GetStringMapString("resolve_through"),
ProxyCheckInterval: viper.GetInt("proxy_check_interval"), ProxyCheckInterval: viper.GetInt("proxy_check_interval"),
ProxyCheckImpersonateChrome: viper.GetBool("proxy_check_impersonate_chrome"),
ProxyCheckImpersonateChromeBinary: viper.GetString("proxy_check_impersonate_chrome_binary"),
ProxyConnectRetries: viper.GetInt("proxy_connect_retries"),
} }
if len(config.ProxyPoolOurs) == 0 && len(config.ProxyPoolThirdparty) == 0 { if len(config.ProxyPoolOurs) == 0 && len(config.ProxyPoolThirdparty) == 0 {
@ -98,21 +86,6 @@ func SetConfig(configFile string) (*Config, error) {
return nil, proxyPoolThirdpartyErr return nil, proxyPoolThirdpartyErr
} }
if config.ProxyCheckImpersonateChrome {
if _, err := os.Stat(config.ProxyCheckImpersonateChromeBinary); os.IsNotExist(err) {
return nil, errors.New(fmt.Sprintf(`curl-impersonate-chrome binary does not exist: "%s"`, config.ProxyCheckImpersonateChromeBinary))
}
cmd := exec.Command(config.ProxyCheckImpersonateChromeBinary, "--help")
err = cmd.Run()
if err != nil {
return nil, errors.New(fmt.Sprintf(`curl-impersonate-chrome binary failed to run: %s`, err))
}
}
if config.ProxyConnectRetries <= 0 {
return nil, errors.New("proxy_connect_retries must be greater than 0")
}
cfg = config cfg = config
return config, nil return config, nil
} }

View File

@ -1,27 +0,0 @@
package config
import "flag"
var CliArgs *CliConfig
type CliConfig struct {
ConfigFile string
Debug bool
Help bool
Version bool
LogThirdPartyTest bool
}
func ParseArgs() {
if CliArgs != nil {
panic("already defined")
}
CliArgs = &CliConfig{}
flag.StringVar(&CliArgs.ConfigFile, "config", "", "Path to the config file")
flag.BoolVar(&CliArgs.Debug, "d", false, "Enable debug mode")
flag.BoolVar(&CliArgs.Debug, "debug", false, "Enable debug mode")
flag.BoolVar(&CliArgs.Debug, "l", false, "Log third-party test debug info")
flag.BoolVar(&CliArgs.Debug, "log-third-party-test-failures", false, "Log third-party test debug info")
flag.BoolVar(&CliArgs.Version, "v", false, "Print version and exit")
flag.Parse()
}

View File

@ -13,17 +13,26 @@ import (
"runtime/debug" "runtime/debug"
) )
type cliConfig struct {
configFile string
initialCrawl bool
debug bool
disableElasticSync bool
help bool
version bool
}
var Version = "development" var Version = "development"
var VersionDate = "not set" var VersionDate = "not set"
func main() { func main() {
fmt.Println("=== Proxy Load Balancer ===") fmt.Println("=== Proxy Load Balancer ===")
config.ParseArgs() cliArgs := parseArgs()
if config.CliArgs.Help { if cliArgs.help {
flag.Usage() flag.Usage()
os.Exit(0) os.Exit(0)
} }
if config.CliArgs.Version { if cliArgs.version {
buildInfo, ok := debug.ReadBuildInfo() buildInfo, ok := debug.ReadBuildInfo()
if ok { if ok {
@ -42,7 +51,7 @@ func main() {
os.Exit(0) os.Exit(0)
} }
if config.CliArgs.Debug { if cliArgs.debug {
logging.InitLogger(logrus.DebugLevel) logging.InitLogger(logrus.DebugLevel)
} else { } else {
logging.InitLogger(logrus.InfoLevel) logging.InitLogger(logrus.InfoLevel)
@ -50,7 +59,7 @@ func main() {
log := logging.GetLogger() log := logging.GetLogger()
log.Debugln("Initializing...") log.Debugln("Initializing...")
if config.CliArgs.ConfigFile == "" { if cliArgs.configFile == "" {
exePath, err := os.Executable() exePath, err := os.Executable()
if err != nil { if err != nil {
panic(err) panic(err)
@ -61,24 +70,20 @@ func main() {
if _, err := os.Stat(filepath.Join(exeDir, "config.yaml")); err == nil { if _, err := os.Stat(filepath.Join(exeDir, "config.yaml")); err == nil {
log.Fatalln("Both config.yml and config.yaml exist in the executable directory. Please specify one with the --config flag.") log.Fatalln("Both config.yml and config.yaml exist in the executable directory. Please specify one with the --config flag.")
} }
config.CliArgs.ConfigFile = filepath.Join(exeDir, "config.yml") cliArgs.configFile = filepath.Join(exeDir, "config.yml")
} else if _, err := os.Stat(filepath.Join(exeDir, "config.yaml")); err == nil { } else if _, err := os.Stat(filepath.Join(exeDir, "config.yaml")); err == nil {
config.CliArgs.ConfigFile = filepath.Join(exeDir, "config.yaml") cliArgs.configFile = filepath.Join(exeDir, "config.yaml")
} else { } else {
log.Fatalln("No config file found in the executable directory. Please provide one with the --config flag.") log.Fatalln("No config file found in the executable directory. Please provide one with the --config flag.")
} }
} }
configData, err := config.SetConfig(config.CliArgs.ConfigFile) configData, err := config.SetConfig(cliArgs.configFile)
if err != nil { if err != nil {
log.Fatalf(`Failed to load config: %s`, err) log.Fatalf(`Failed to load config: %s`, err)
} }
log.Debugf(`Proxy check interval: %d sec`, config.GetConfig().ProxyCheckInterval) log.Debugf(`Proxy check interval: %d sec`, config.GetConfig().ProxyCheckInterval)
if config.GetConfig().ProxyCheckImpersonateChrome {
log.Debugf(`Using curl-impersonate binary: %s`, config.GetConfig().ProxyCheckImpersonateChromeBinary)
}
proxyCluster := proxy.NewForwardProxyCluster() proxyCluster := proxy.NewForwardProxyCluster()
go func() { go func() {
log.Fatal(http.ListenAndServe(":"+configData.HTTPPort, proxyCluster)) log.Fatal(http.ListenAndServe(":"+configData.HTTPPort, proxyCluster))
@ -91,3 +96,13 @@ func main() {
select {} select {}
} }
func parseArgs() cliConfig {
var cliArgs cliConfig
flag.StringVar(&cliArgs.configFile, "config", "", "Path to the config file")
flag.BoolVar(&cliArgs.debug, "d", false, "Enable debug mode")
flag.BoolVar(&cliArgs.debug, "debug", false, "Enable debug mode")
flag.BoolVar(&cliArgs.version, "v", false, "Print version and exit")
flag.Parse()
return cliArgs
}

View File

@ -1,17 +1,11 @@
package proxy package proxy
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
"main/config" "main/config"
"net/http" "net/http"
"net/url" "net/url"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
) )
func sendRequestThroughProxy(pxy string, targetURL string) (string, error) { func sendRequestThroughProxy(pxy string, targetURL string) (string, error) {
@ -23,8 +17,6 @@ func sendRequestThroughProxy(pxy string, targetURL string) (string, error) {
if proxyUser != "" && proxyPass != "" { if proxyUser != "" && proxyPass != "" {
parsedProxyUrl.User = url.UserPassword(proxyUser, proxyPass) parsedProxyUrl.User = url.UserPassword(proxyUser, proxyPass)
} }
if !config.GetConfig().ProxyCheckImpersonateChrome {
transport := &http.Transport{ transport := &http.Transport{
Proxy: http.ProxyURL(parsedProxyUrl), Proxy: http.ProxyURL(parsedProxyUrl),
} }
@ -41,15 +33,15 @@ func sendRequestThroughProxy(pxy string, targetURL string) (string, error) {
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7") req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
req.Header.Set("Accept-Language", "en-US,en;q=0.9") req.Header.Set("Accept-Language", "en-US,en;q=0.9")
req.Header.Set("Priority", "u=0, i") req.Header.Set("Priority", "u=0, i")
req.Header.Set("Sec-Ch-Ua", `Chromium";v="116", "Not)A;Brand";v="24", "Google Chrome";v="116"`) req.Header.Set("Sec-Ch-Ua", `"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"`)
req.Header.Set("Sec-Ch-Ua-Mobile", "?0") req.Header.Set("Sec-Ch-Ua-Mobile", "?0")
req.Header.Set("Sec-Ch-Ua-Platform", `"Windows"`) req.Header.Set("Sec-Ch-Ua-Platform", `"Windows"`)
req.Header.Set("Sec-Fetch-Dest", "document") req.Header.Set("Sec-Fetch-Dest", "document")
req.Header.Set("Sec-Fetch-Mode", "navigate") req.Header.Set("Sec-Fetch-Mode", "navigate")
req.Header.Set("Sec-Fetch-Site", "none") req.Header.Set("Sec-Fetch-Site", "cross-site")
req.Header.Set("Sec-Fetch-User", "?1") req.Header.Set("Sec-Fetch-User", "?1")
req.Header.Set("Upgrade-Insecure-Requests", "1") req.Header.Set("Upgrade-Insecure-Requests", "1")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36") req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36")
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
@ -64,87 +56,4 @@ func sendRequestThroughProxy(pxy string, targetURL string) (string, error) {
return string(bodyBytes), nil return string(bodyBytes), nil
} }
return "", fmt.Errorf("bad response code %d", resp.StatusCode) return "", fmt.Errorf("bad response code %d", resp.StatusCode)
} else {
tmpfile, err := os.CreateTemp("", "response")
if err != nil {
log.Fatal(err)
}
defer os.Remove(tmpfile.Name()) // clean up
cmd := exec.Command(
config.GetConfig().ProxyCheckImpersonateChromeBinary,
"--proxy",
parsedProxyUrl.String(),
"-o",
tmpfile.Name(),
"-w",
"%{http_code}",
"--ciphers",
"TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-CHACHA20-POLY1305,ECDHE-RSA-CHACHA20-POLY1305,ECDHE-RSA-AES128-SHA,ECDHE-RSA-AES256-SHA,AES128-GCM-SHA256,AES256-GCM-SHA384,AES128-SHA,AES256-SHA",
"-H",
`sec-ch-ua: "Chromium";v="116", "Not)A;Brand";v="24", "Google Chrome";v="116"`,
"-H",
`sec-ch-ua-mobile: ?0`,
"-H",
`sec-ch-ua-platform: "Windows"`,
"-H",
`Upgrade-Insecure-Requests: 1`,
"-H",
`User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36`,
"-H",
`Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7`,
"-H",
`Sec-Fetch-Site: none`,
"-H",
`Sec-Fetch-Mode: navigate`,
"-H",
`Sec-Fetch-User: ?1`,
"-H",
`Sec-Fetch-Dest: document`,
"-H",
`Accept-Encoding: gzip, deflate, br`,
"-H",
`Accept-Language: en-US,en;q=0.9`,
"--http2",
"--http2-no-server-push",
"--compressed",
"--tlsv1.2",
"--alps",
"--tls-permute-extensions",
"--cert-compression",
"brotli",
targetURL,
)
output, err := cmd.Output()
if err != nil {
var exitError *exec.ExitError
if errors.As(err, &exitError) {
return "", fmt.Errorf("command exited with code %d: %s", exitError.ExitCode(), exitError.Stderr)
}
return "", err
}
outputStr := strings.TrimSpace(string(output))
match, _ := regexp.MatchString(`^\d{3}$`, outputStr)
if !match {
return "", fmt.Errorf("unexpected output from curl command: %s", outputStr)
}
statusCode, err := strconv.Atoi(outputStr)
if err != nil {
return "", err
}
if statusCode != http.StatusOK {
return "", fmt.Errorf("bad response code %d", statusCode)
}
body, err := os.ReadFile(tmpfile.Name())
if err != nil {
return "", err
}
return string(body), nil
}
} }

View File

@ -90,6 +90,7 @@ func (p *ForwardProxyCluster) validateRequestAndGetProxy(w http.ResponseWriter,
} }
return selectedProxy, proxyUser, proxyPass, proxyHost, parsedProxyUrl, nil return selectedProxy, proxyUser, proxyPass, proxyHost, parsedProxyUrl, nil
} }
func (p *ForwardProxyCluster) proxyHttpConnect(w http.ResponseWriter, req *http.Request) { func (p *ForwardProxyCluster) proxyHttpConnect(w http.ResponseWriter, req *http.Request) {
@ -97,6 +98,7 @@ func (p *ForwardProxyCluster) proxyHttpConnect(w http.ResponseWriter, req *http.
remoteAddr, _, _ := net.SplitHostPort(req.RemoteAddr) remoteAddr, _, _ := net.SplitHostPort(req.RemoteAddr)
_, proxyUser, proxyPass, proxyHost, parsedProxyUrl, err := p.validateRequestAndGetProxy(w, req) _, proxyUser, proxyPass, proxyHost, parsedProxyUrl, err := p.validateRequestAndGetProxy(w, req)
if err != nil { if err != nil {
// Error has already been handled, just log and return.
if proxyHost == "" { if proxyHost == "" {
proxyHost = "none" proxyHost = "none"
} }
@ -104,6 +106,7 @@ func (p *ForwardProxyCluster) proxyHttpConnect(w http.ResponseWriter, req *http.
return return
} }
// Variables for later
var returnCode *int var returnCode *int
returnCode = new(int) returnCode = new(int)
*returnCode = -1 *returnCode = -1
@ -133,25 +136,18 @@ func (p *ForwardProxyCluster) proxyHttpConnect(w http.ResponseWriter, req *http.
copyHeader(proxyReq.Header, req.Header) copyHeader(proxyReq.Header, req.Header)
proxyReq.Header.Set("X-Forwarded-For", req.RemoteAddr) proxyReq.Header.Set("X-Forwarded-For", req.RemoteAddr)
for i := 0; i < config.GetConfig().ProxyConnectRetries; i++ { // Retry mechanic
resp, err := client.Do(proxyReq) resp, err := client.Do(proxyReq)
if err != nil { if err != nil {
*errorMsg = fmt.Sprintf(`Failed to execute %s request to "%s" - attempt %d/%d - %s`, req.Method, req.URL.String(), i+1, config.GetConfig().ProxyConnectRetries, err) *errorMsg = fmt.Sprintf(`Failed to execute %s request to "%s": %s`, req.Method, req.URL.String(), err)
if i < config.GetConfig().ProxyConnectRetries-1 {
continue
} else {
http.Error(w, "failed to execute request to downstream", http.StatusServiceUnavailable) http.Error(w, "failed to execute request to downstream", http.StatusServiceUnavailable)
return return
} }
} else {
defer resp.Body.Close() defer resp.Body.Close()
*returnCode = resp.StatusCode *returnCode = resp.StatusCode
copyHeader(w.Header(), resp.Header) copyHeader(w.Header(), resp.Header)
w.WriteHeader(resp.StatusCode) w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body) io.Copy(w, resp.Body)
break
}
}
} }
func (p *ForwardProxyCluster) proxyHttpsConnect(w http.ResponseWriter, req *http.Request) { func (p *ForwardProxyCluster) proxyHttpsConnect(w http.ResponseWriter, req *http.Request) {
@ -160,6 +156,7 @@ func (p *ForwardProxyCluster) proxyHttpsConnect(w http.ResponseWriter, req *http
targetHost, _, _ := net.SplitHostPort(req.Host) targetHost, _, _ := net.SplitHostPort(req.Host)
_, proxyUser, proxyPass, proxyHost, _, err := p.validateRequestAndGetProxy(w, req) _, proxyUser, proxyPass, proxyHost, _, err := p.validateRequestAndGetProxy(w, req)
if err != nil { if err != nil {
// Error has already been handled, just log and return.
if proxyHost == "" { if proxyHost == "" {
proxyHost = "none" proxyHost = "none"
} }
@ -174,22 +171,13 @@ func (p *ForwardProxyCluster) proxyHttpsConnect(w http.ResponseWriter, req *http
*errorMsg = "" *errorMsg = ""
defer logProxyRequest(remoteAddr, proxyHost, targetHost, returnCode, "CONNECT", requestStartTime, errorMsg) defer logProxyRequest(remoteAddr, proxyHost, targetHost, returnCode, "CONNECT", requestStartTime, errorMsg)
var proxyConn net.Conn
for i := 0; i < config.GetConfig().ProxyConnectRetries; i++ {
// Start a connection to the downstream proxy server. // Start a connection to the downstream proxy server.
proxyConn, err = net.DialTimeout("tcp", proxyHost, config.GetConfig().ProxyConnectTimeout) proxyConn, err := net.DialTimeout("tcp", proxyHost, config.GetConfig().ProxyConnectTimeout)
if err != nil { if err != nil {
*errorMsg = fmt.Sprintf(`Failed to dial proxy %s - attempt %d/%d - %s`, proxyHost, i+1, config.GetConfig().ProxyConnectRetries, err) *errorMsg = fmt.Sprintf(`Failed to dial proxy %s - %s`, proxyHost, err)
if i < config.GetConfig().ProxyConnectRetries-1 {
continue
} else {
http.Error(w, "failed to make request to downstream", http.StatusServiceUnavailable) http.Error(w, "failed to make request to downstream", http.StatusServiceUnavailable)
return return
} }
} else {
break
}
}
// Proxy authentication // Proxy authentication
auth := fmt.Sprintf("%s:%s", proxyUser, proxyPass) auth := fmt.Sprintf("%s:%s", proxyUser, proxyPass)

View File

@ -10,8 +10,6 @@ import (
"time" "time"
) )
// TODO: fix 503 errors returned during proxy checking process
func (p *ForwardProxyCluster) ValidateProxiesThread() { func (p *ForwardProxyCluster) ValidateProxiesThread() {
log.Infoln("Doing initial backend check, please wait...") log.Infoln("Doing initial backend check, please wait...")
started := false started := false
@ -60,13 +58,10 @@ func (p *ForwardProxyCluster) ValidateProxiesThread() {
// Test the proxy. // Test the proxy.
ipAddr, testErr := sendRequestThroughProxy(pxy, config.GetConfig().IpCheckerURL) ipAddr, testErr := sendRequestThroughProxy(pxy, config.GetConfig().IpCheckerURL)
if testErr != nil { if testErr != nil {
if isThirdparty(pxy) {
if config.CliArgs.LogThirdPartyTest {
log.Debugf("Validate - %s failed: %s", proxyHost, testErr) log.Debugf("Validate - %s failed: %s", proxyHost, testErr)
} if isThirdparty(pxy) {
newThirdpartyOfflineProxies = append(newThirdpartyOfflineProxies, pxy) newThirdpartyOfflineProxies = append(newThirdpartyOfflineProxies, pxy)
} else { } else {
log.Debugf("Validate - %s failed: %s", proxyHost, testErr)
newOurOfflineProxies = append(newOurOfflineProxies, pxy) newOurOfflineProxies = append(newOurOfflineProxies, pxy)
} }
return return
@ -90,9 +85,7 @@ func (p *ForwardProxyCluster) ValidateProxiesThread() {
if bv3hiErr != nil { if bv3hiErr != nil {
okToAdd = false okToAdd = false
newThirdpartyBrokenProxies = append(newThirdpartyBrokenProxies, pxy) newThirdpartyBrokenProxies = append(newThirdpartyBrokenProxies, pxy)
if config.CliArgs.LogThirdPartyTest { log.Debugf("Validate - %s failed third-party test: %s", proxyHost, bv3hiErr)
log.Debugf(`%s failed third-party test for URL "%s" -- %s`, proxyHost, d, bv3hiErr)
}
break break
} }
} }