Golang并发使用HTTP代理的最佳实践

Golang并发使用HTTP代理的最佳实践 在我的程序中有一个传输器 多个goroutine发送HTTP请求 如果某个请求的响应状态码是403,我会为该请求更换代理 这会影响其他goroutine吗?

package main

import (
"io/ioutil"
"log"
"net"
"net/http"
"net/url"
"os"
"os/signal"
"syscall"
"time"
)

func main() {
client := &http.Client{
	Transport: &http.Transport{
		//Proxy: http.ProxyFromEnvironment,
		DialContext: (&net.Dialer{
			Timeout:   30 * time.Second,
			KeepAlive: 30 * time.Second,
		}).DialContext,
		MaxIdleConns:          100,
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
	},
}

go func() {
	req, _ := http.NewRequest(http.MethodGet, "https://www.google.com", nil)
    client.Transport.(*http.Transport).Proxy = func(request *http.Request) (url *url.URL, e error) {
		return url.Parse("http://10.101.32.12")
	}
	
	response, err := client.Do(req)
	if err != nil {
		log.Println(err)
		return
	}

	defer response.Body.Close()
	data, _ := ioutil.ReadAll(response.Body)
	log.Println(string(data))

}()

go func() {
	req, _ := http.NewRequest(http.MethodGet, "http://www.baidu.com", nil)
	client.Transport.(*http.Transport).Proxy = func(request *http.Request) (url *url.URL, e error) {
		return url.Parse("http://10.101.32.13")
	}
	response, err := client.Do(req)
	if err != nil {
		log.Println(err)
		return
	}

	defer response.Body.Close()
	data, _ := ioutil.ReadAll(response.Body)
	log.Println(string(data))

}()

chOver := make(chan os.Signal, 1)
signal.Notify(chOver, syscall.SIGINT, syscall.SIGTERM)
<-chOver

第一个goroutine也会使用代理。


更多关于Golang并发使用HTTP代理的最佳实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

感谢您的回答,我已经添加了一些示例代码

更多关于Golang并发使用HTTP代理的最佳实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你有示例代码可以展示吗?

原则上,每个请求都会获得自己的 goroutine,因此写入该请求头部或主体的任何输出都独立于其他请求。

在你的代码中,多个goroutine共享同一个http.Client实例,并且直接修改其TransportProxy字段。这会导致并发访问问题,因为一个goroutine修改代理设置时,可能会影响其他正在执行或即将执行的请求。

问题分析

当多个goroutine并发修改Transport.Proxy时,会产生数据竞争。即使没有数据竞争,代理设置的修改也会影响所有使用该client的后续请求。

解决方案

为每个goroutine创建独立的http.Client实例,或者使用请求级别的代理配置:

方案1:为每个goroutine创建独立的Client

package main

import (
    "io/ioutil"
    "log"
    "net"
    "net/http"
    "net/url"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func createClientWithProxy(proxyURL string) *http.Client {
    proxy := func(_ *http.Request) (*url.URL, error) {
        return url.Parse(proxyURL)
    }
    
    return &http.Client{
        Transport: &http.Transport{
            Proxy: proxy,
            DialContext: (&net.Dialer{
                Timeout:   30 * time.Second,
                KeepAlive: 30 * time.Second,
            }).DialContext,
            MaxIdleConns:          100,
            IdleConnTimeout:       90 * time.Second,
            TLSHandshakeTimeout:   10 * time.Second,
            ExpectContinueTimeout: 1 * time.Second,
        },
    }
}

func main() {
    go func() {
        client := createClientWithProxy("http://10.101.32.12")
        req, _ := http.NewRequest(http.MethodGet, "https://www.google.com", nil)
        
        response, err := client.Do(req)
        if err != nil {
            log.Println(err)
            return
        }

        defer response.Body.Close()
        data, _ := ioutil.ReadAll(response.Body)
        log.Println(string(data))
    }()

    go func() {
        client := createClientWithProxy("http://10.101.32.13")
        req, _ := http.NewRequest(http.MethodGet, "http://www.baidu.com", nil)
        
        response, err := client.Do(req)
        if err != nil {
            log.Println(err)
            return
        }

        defer response.Body.Close()
        data, _ := ioutil.ReadAll(response.Body)
        log.Println(string(data))
    }()

    chOver := make(chan os.Signal, 1)
    signal.Notify(chOver, syscall.SIGINT, syscall.SIGTERM)
    <-chOver
}

方案2:动态代理切换(处理403状态码)

package main

import (
    "io/ioutil"
    "log"
    "net"
    "net/http"
    "net/url"
    "os"
    "os/signal"
    "sync"
    "syscall"
    "time"
)

type ProxyManager struct {
    proxies []string
    mu      sync.RWMutex
    current int
}

func NewProxyManager(proxies []string) *ProxyManager {
    return &ProxyManager{
        proxies: proxies,
        current: 0,
    }
}

func (pm *ProxyManager) GetCurrentProxy() string {
    pm.mu.RLock()
    defer pm.mu.RUnlock()
    if len(pm.proxies) == 0 {
        return ""
    }
    return pm.proxies[pm.current]
}

func (pm *ProxyManager) RotateProxy() {
    pm.mu.Lock()
    defer pm.mu.Unlock()
    if len(pm.proxies) > 1 {
        pm.current = (pm.current + 1) % len(pm.proxies)
    }
}

func (pm *ProxyManager) CreateClient() *http.Client {
    proxyFunc := func(_ *http.Request) (*url.URL, error) {
        proxyURL := pm.GetCurrentProxy()
        if proxyURL == "" {
            return nil, nil
        }
        return url.Parse(proxyURL)
    }
    
    return &http.Client{
        Transport: &http.Transport{
            Proxy: proxyFunc,
            DialContext: (&net.Dialer{
                Timeout:   30 * time.Second,
                KeepAlive: 30 * time.Second,
            }).DialContext,
            MaxIdleConns:          100,
            IdleConnTimeout:       90 * time.Second,
            TLSHandshakeTimeout:   10 * time.Second,
            ExpectContinueTimeout: 1 * time.Second,
        },
    }
}

func main() {
    proxies := []string{
        "http://10.101.32.12",
        "http://10.101.32.13",
        "http://10.101.32.14",
    }
    
    proxyManager := NewProxyManager(proxies)
    
    // 每个goroutine使用独立的client,但共享代理管理器
    for i := 0; i < 3; i++ {
        go func(id int) {
            client := proxyManager.CreateClient()
            req, _ := http.NewRequest(http.MethodGet, "https://httpbin.org/status/403", nil)
            
            response, err := client.Do(req)
            if err != nil {
                log.Printf("Goroutine %d error: %v", id, err)
                return
            }
            
            defer response.Body.Close()
            
            if response.StatusCode == 403 {
                log.Printf("Goroutine %d got 403, rotating proxy", id)
                proxyManager.RotateProxy()
                // 可以在这里重试请求
            } else {
                data, _ := ioutil.ReadAll(response.Body)
                log.Printf("Goroutine %d response: %s", id, string(data))
            }
        }(i)
    }

    chOver := make(chan os.Signal, 1)
    signal.Notify(chOver, syscall.SIGINT, syscall.SIGTERM)
    <-chOver
}

在第一个方案中,每个goroutine有自己独立的client实例,互不干扰。在第二个方案中,通过代理管理器来协调多个goroutine的代理使用,当检测到403状态码时可以自动切换代理。

回到顶部