File: dialer.go

package info (click to toggle)
golang-github-valyala-fasthttp 1%3A1.59.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,932 kB
  • sloc: makefile: 34
file content (263 lines) | stat: -rw-r--r-- 7,949 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
package fasthttpproxy

import (
	"bufio"
	"encoding/base64"
	"errors"
	"fmt"
	"net"
	"net/url"
	"strings"
	"sync"
	"time"

	"github.com/valyala/fasthttp"
	"golang.org/x/net/http/httpproxy"
	"golang.org/x/net/proxy"
)

var (
	// Used for caching authentication information when using an HTTP proxy,
	// it helps avoid re-encoding the authentication details when the ProxyURL
	// changes along with the request URL.
	authCache    = sync.Map{}
	colonTLSPort = ":443"
	tmpURL       = &url.URL{Scheme: httpsScheme, Host: "example.com"}
)

// Dialer embeds both fasthttp.TCPDialer and httpproxy.Config, allowing it
// to take advantage of the optimizations provided by fasthttp for dialing while also
// utilizing the finer-grained configuration options offered by httpproxy.
type Dialer struct {
	fasthttp.TCPDialer
	// Support HTTPProxy, HTTPSProxy and NoProxy configuration.
	//
	// HTTPProxy represents the value of the HTTP_PROXY or
	// http_proxy environment variable. It will be used as the proxy
	// URL for HTTP requests unless overridden by NoProxy.
	//
	// HTTPSProxy represents the HTTPS_PROXY or https_proxy
	// environment variable. It will be used as the proxy URL for
	// HTTPS requests unless overridden by NoProxy.
	//
	// NoProxy represents the NO_PROXY or no_proxy environment
	// variable. It specifies a string that contains comma-separated values
	// specifying hosts that should be excluded from proxying. Each value is
	// represented by an IP address prefix (1.2.3.4), an IP address prefix in
	// CIDR notation (1.2.3.4/8), a domain name, or a special DNS label (*).
	// An IP address prefix and domain name can also include a literal port
	// number (1.2.3.4:80).
	// A domain name matches that name and all subdomains. A domain name with
	// a leading "." matches subdomains only. For example "foo.com" matches
	// "foo.com" and "bar.foo.com"; ".y.com" matches "x.y.com" but not "y.com".
	// A single asterisk (*) indicates that no proxying should be done.
	// A best effort is made to parse the string and errors are
	// ignored.
	httpproxy.Config
	// Attempt to connect to both ipv4 and ipv6 addresses if set to true.
	// By default, dial only to ipv4 addresses,
	// since unfortunately ipv6 remains broken in many networks worldwide :)
	//
	// This field from the fasthttp client is provided redundantly here because
	// when we customize the Dial function for the client, its DialDualStack field
	// configuration becomes ineffective.
	DialDualStack bool
	// Dial timeout.
	//
	// This field from the fasthttp client is provided redundantly here because
	// when we customize the Dial function for the client, its DialTimeout field
	// configuration becomes ineffective.
	Timeout time.Duration
	// The timeout for sending a CONNECT request when using an HTTP proxy.
	ConnectTimeout time.Duration
}

// GetDialFunc method returns a fasthttp-style dial function. The useEnv parameter
// determines whether the proxy address comes from Dialer.Config or from environment variables.
func (d *Dialer) GetDialFunc(useEnv bool) (dialFunc fasthttp.DialFunc, err error) {
	config := &d.Config
	if useEnv {
		config = httpproxy.FromEnvironment()
	}
	proxyURLIsSame := false
	if config.HTTPSProxy == config.HTTPProxy && config.NoProxy == "" {
		proxyURLIsSame = true
	}
	network := "tcp4"
	if d.DialDualStack {
		network = "tcp"
	}
	proxyFunc := config.ProxyFunc()
	if proxyURLIsSame {
		var proxyURL *url.URL
		var proxyDialer proxy.Dialer
		proxyURL, err = proxyFunc(tmpURL)
		if err != nil {
			return nil, err
		}
		if proxyURL == nil {
			// dial directly
			return func(addr string) (net.Conn, error) {
				return d.Dial(network, addr)
			}, nil
		}
		switch proxyURL.Scheme {
		case "socks5", "socks5h":
			proxyDialer, err = proxy.FromURL(proxyURL, d)
			if err != nil {
				return
			}
		case "http":
			proxyAddr, auth := addrAndAuth(proxyURL)
			proxyDialer = DialerFunc(func(network, addr string) (conn net.Conn, err error) {
				return httpProxyDial(d, network, addr, proxyAddr, auth)
			})
		default:
			return nil, errors.New("proxy: unknown scheme: " + proxyURL.Scheme)
		}
		return func(addr string) (net.Conn, error) {
			return proxyDialer.Dial(network, addr)
		}, nil
	}
	// slow path when the proxyURL changes along with the request URL.
	return func(addr string) (conn net.Conn, err error) {
		var proxyDialer proxy.Dialer
		var proxyURL *url.URL
		scheme := httpsScheme
		if !strings.HasSuffix(addr, colonTLSPort) {
			scheme = httpScheme
		}
		reqURL := &url.URL{Host: addr, Scheme: scheme}
		proxyURL, err = proxyFunc(reqURL)
		if err != nil {
			return
		}
		if proxyURL == nil {
			// dial directly
			return d.Dial(network, addr)
		}
		switch proxyURL.Scheme {
		case "socks5", "socks5h":
			proxyDialer, err = proxy.FromURL(proxyURL, d)
			if err != nil {
				return
			}
		case "http":
			proxyAddr, auth := addrAndAuth(proxyURL)
			proxyDialer = DialerFunc(func(network, addr string) (conn net.Conn, err error) {
				return httpProxyDial(d, network, addr, proxyAddr, auth)
			})
		default:
			return nil, errors.New("proxy: unknown scheme: " + proxyURL.Scheme)
		}
		return proxyDialer.Dial(network, addr)
	}, nil
}

// Dial is solely for implementing the proxy.Dialer interface.
func (d *Dialer) Dial(network, addr string) (conn net.Conn, err error) {
	if network == "tcp4" {
		if d.Timeout > 0 {
			return d.TCPDialer.DialTimeout(addr, d.Timeout)
		}
		return d.TCPDialer.Dial(addr)
	}
	if network == "tcp" {
		if d.Timeout > 0 {
			return d.TCPDialer.DialDualStackTimeout(addr, d.Timeout)
		}
		return d.TCPDialer.DialDualStack(addr)
	}
	err = errors.New("dont support the network: " + network)
	return
}

func (d *Dialer) connectTimeout() time.Duration {
	return d.ConnectTimeout
}

// In the httpProxyDial function, the proxy.Dialer that implements
// this interface can retrieve timeout information when sending the CONNECT
// method to the HTTP proxy.
type httpProxyDialer interface {
	connectTimeout() time.Duration
}

// DialerFunc Make a function of type func(network, addr string) (net.Conn, error)
// implement the proxy.Dialer interface.
type DialerFunc func(network, addr string) (net.Conn, error)

func (d DialerFunc) Dial(network, addr string) (net.Conn, error) {
	return d(network, addr)
}

// Establish a connection through an HTTP proxy.
func httpProxyDial(dialer proxy.Dialer, network, addr, proxyAddr, auth string) (conn net.Conn, err error) {
	conn, err = dialer.Dial(network, proxyAddr)
	if err != nil {
		return
	}
	var connectTimeout time.Duration
	hp, ok := dialer.(httpProxyDialer)
	if ok {
		connectTimeout = hp.connectTimeout()
	}

	if connectTimeout > 0 {
		if err = conn.SetDeadline(time.Now().Add(connectTimeout)); err != nil {
			_ = conn.Close()
			return nil, err
		}
		defer func() {
			_ = conn.SetDeadline(time.Time{})
		}()
	}
	req := "CONNECT " + addr + " HTTP/1.1\r\nHost: " + addr + "\r\n"
	if auth != "" {
		req += "Proxy-Authorization: Basic " + auth + "\r\n"
	}
	req += "\r\n"
	_, err = conn.Write([]byte(req))
	if err != nil {
		_ = conn.Close()
		return
	}
	res := fasthttp.AcquireResponse()
	defer fasthttp.ReleaseResponse(res)
	res.SkipBody = true
	if err = res.Read(bufio.NewReaderSize(conn, 1024)); err != nil {
		_ = conn.Close()
		return
	}
	if res.Header.StatusCode() != 200 {
		_ = conn.Close()
		err = fmt.Errorf("could not connect to proxyAddr: %s status code: %d", proxyAddr, res.Header.StatusCode())
		return
	}
	return
}

// Cache authentication information for HTTP proxies.
type proxyInfo struct {
	auth string
	addr string
}

func addrAndAuth(pu *url.URL) (proxyAddr, auth string) {
	if pu.User == nil {
		proxyAddr = pu.Host + pu.Path
		return
	}
	var info *proxyInfo
	v, ok := authCache.Load(pu)
	if ok {
		info = v.(*proxyInfo)
		return info.addr, info.auth
	}
	info = &proxyInfo{
		auth: base64.StdEncoding.EncodeToString([]byte(pu.User.String())),
		addr: pu.Host + pu.Path,
	}
	authCache.Store(pu, info)
	return info.addr, info.auth
}