让Go的HTTP客户端走socks5代理
链路 现在通常使用Socks5
协议的实现shadowsocks
翻墙,再配合Privoxy HTTP
代理把HTTP
转化成Socks5
协议。具体过程如下:
1 app -HTTP-> Privoxy -socks5-> Socks5Client -encryption-> Socks5Server -HTTP-> Server
现在我们希望app
也能直接 走Socks5
代理,即:
1 app -socks5-encryption-data-> Socks5Server -HTTP-> Server
这样的好处是只要Socks5Server
服务搭建好了,就可以让我们的应用代码直接翻墙,而无需考虑操作系统环境。
为此我们需要做两件事:
实现socks5
协议
让HTTP
走socks5
通道
第一点我们直接使用开源模块shadowsocks-go
,第二点通过Go
的HTTP
包的底层实现原理,让网络请求走shadowsocks-go
的pipe
。接下来分别讲这两点。
shadowsocks-go shadowsocks-go
是实现socks5
协议的开源软件,除了用于翻墙还可以当做普通模块使用。本文需要使用到它的TCP
加密通道。使用很简单,以一段代码为示例:
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 package mainimport ( ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" "log" "net" "strconv" ) var Config struct { server string port int password string method string } func Socks5Conn (addr string , config *Config) (net.Conn, error ) { rawAddr, err := ss.RawAddr(addr) if err != nil { return nil , err } serverAddr := net.JoinHostPort(config.server, strconv.Itoa(config.port)) cipher, err := ss.NewCipher(config.method, config.password) if err != nil { return nil , err } return ss.DialWithRawAddr(rawAddr, serverAddr, cipher) }
Socks5Conn
的作用是通过输入访问目标服务器的地址(IP,Port组合),返回经过socks5
加密的网络连接对象net.Conn
(其实是一个接口interface
)。这个函数的核心是ss.DialWithRawAddr
,它通过目标服务器的地址和socks5
服务器以及加密模块即可返回经过socks5
加密的网络连接对象net.Conn
。这个网络连接对象供HTTP
连接使用。
Go HTTP 模块中的 Transport 对象 Go
的HTTP
标准库的实现原理分为两个层次。一层负责HTTP
语义的处理包括Header
、URL
、表单
,另外一层负责网络连接。前者的相关对象包括Request
、Response
,后者则由Transport
对象复杂处理。
通过Transport
对象的源码
1 2 3 4 5 6 7 8 9 10 type Transport struct { DialContext func (ctx context.Context, network, addr string ) (net.Conn, error ) Dial func (network, addr string ) (net.Conn, error ) }
可知,负责底层网络连接的函数为:DialContext
和Dial
,后者已经弃用。由于shadowsocks-go
实现原因和出于化简实现的目的,本文依旧Dial
函数实现。
指定Transport
使用socks5
的网络连接对象和设置Client
对象使用该Transport
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func HTTPClientBySocks5 (addr string ) *http.Client { rawAddr, err := ss.RawAddr(addr) handleError(err) serverAddr := net.JoinHostPort(config.server, strconv.Itoa(config.port)) cipher, err := ss.NewCipher(config.method, config.password) handleError(err) dailFunc := func (network, addr string ) (net.Conn, error ) { return ss.DialWithRawAddr(rawAddr, serverAddr, cipher.Copy()) } tr := &http.Transport{ MaxIdleConns: 100 , IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, } tr.Dial = dailFunc return &http.Client{Transport: tr} }
此时,就可以直接使用*http.Client
对象做网络请求了。例如实现GET
和POST
方法:
1 2 3 4 5 6 7 8 9 func Get (uri string ) (resp *http.Response, err error ) { client := HTTPClientBySocks5(uri) return client.Get(uri) } func Post (uri string , contentType string , body io.Reader) (resp *http.Response, err error ) { client := HTTPClientBySocks5(uri) return client.Post(uri, contentType, body) }
上述为原理介绍,完整代码如下:
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 package mainimport ( "fmt" ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" "io" "io/ioutil" "log" "net" "net/http" "net/url" "strconv" "time" ) var config struct { server string port int password string method string } func handleError (err error ) { if err != nil { log.Fatal(err) } } func HTTPClientBySocks5 (uri string ) *http.Client { parsedURL, err := url.Parse(uri) handleError(err) host, _, err := net.SplitHostPort(parsedURL.Host) if err != nil { if parsedURL.Scheme == "https" { host = net.JoinHostPort(parsedURL.Host, "443" ) } else { host = net.JoinHostPort(parsedURL.Host, "80" ) } } else { host = parsedURL.Host } rawAddr, err := ss.RawAddr(host) handleError(err) serverAddr := net.JoinHostPort(config.server, strconv.Itoa(config.port)) cipher, err := ss.NewCipher(config.method, config.password) handleError(err) dailFunc := func (network, addr string ) (net.Conn, error ) { return ss.DialWithRawAddr(rawAddr, serverAddr, cipher.Copy()) } tr := &http.Transport{ MaxIdleConns: 100 , IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, } tr.Dial = dailFunc return &http.Client{Transport: tr} } func Get (uri string ) (resp *http.Response, err error ) { client := HTTPClientBySocks5(uri) return client.Get(uri) } func Post (uri string , contentType string , body io.Reader) (resp *http.Response, err error ) { client := HTTPClientBySocks5(uri) return client.Post(uri, contentType, body) } func PostForm (uri string , data url.Values) (resp *http.Response, err error ) { client := HTTPClientBySocks5(uri) return client.PostForm(uri, data) } func Head (uri string ) (resp *http.Response, err error ) { client := HTTPClientBySocks5(uri) return client.Head(uri) } func main () { config.method = "aes-256-cfb" config.password = "your socks pw" config.port = 0 config.server = "your socks ip" var uri string = "https://www.google.com.hk/?gws_rd=ssl" resp, err := Get(uri) handleError(err) defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) handleError(err) fmt.Println(string (body)) }
上面的代码实现下面的app
网络请求流程,可以在应用代码中拿来访问被墙的网站、做加密传输等等。
1 app -socks5-encryption-data-> Socks5Server -HTTP-> Server
事实上,利用上述方法和结合上一篇文章为Redis编写安全通道 ,可以让数据库走socks5
代理。有空详细写写。