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
感谢您的回答,我已经添加了一些示例代码
更多关于Golang并发使用HTTP代理的最佳实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
你有示例代码可以展示吗?
原则上,每个请求都会获得自己的 goroutine,因此写入该请求头部或主体的任何输出都独立于其他请求。
在你的代码中,多个goroutine共享同一个http.Client实例,并且直接修改其Transport的Proxy字段。这会导致并发访问问题,因为一个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状态码时可以自动切换代理。

