

让Go的HTTP客户端走socks5代理
source link: https://allenwind.github.io/blog/5887/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

让Go的HTTP客户端走socks5代理
让Go的HTTP客户端走socks5代理
现在通常使用Socks5
协议的实现shadowsocks
翻墙,再配合Privoxy HTTP
代理把HTTP
转化成Socks5
协议。具体过程如下:
app -HTTP-> Privoxy -socks5-> Socks5Client -encryption-> Socks5Server -HTTP-> Server
现在我们希望app
也能直接走Socks5
代理,即:
app -socks5-encryption-data-> Socks5Server -HTTP-> Server
这样的好处是只要Socks5Server
服务搭建好了,就可以让我们的应用代码直接翻墙,而无需考虑操作系统环境。
为此我们需要做两件事:
- 实现
socks5
协议 - 让
HTTP
走socks5
通道
第一点我们直接使用开源模块shadowsocks-go
,第二点通过Go
的HTTP
包的底层实现原理,让网络请求走shadowsocks-go
的pipe
。接下来分别讲这两点。
shadowsocks-go
shadowsocks-go
是实现socks5
协议的开源软件,除了用于翻墙还可以当做普通模块使用。本文需要使用到它的TCP
加密通道。使用很简单,以一段代码为示例:
package main
import (
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
对象的源码
type Transport struct {
DialContext func(ctx context.Context, network, addr string) (net.Conn, error)
// Dial specifies the dial function for creating unencrypted TCP connections.
//
// Deprecated: Use DialContext instead, which allows the transport
// to cancel dials as soon as they are no longer needed.
// If both are set, DialContext takes priority.
Dial func(network, addr string) (net.Conn, error)
}
可知,负责底层网络连接的函数为:DialContext
和Dial
,后者已经弃用。由于shadowsocks-go
实现原因和出于化简实现的目的,本文依旧Dial
函数实现。
指定Transport
使用socks5
的网络连接对象和设置Client
对象使用该Transport
。
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
方法:
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)
}
上述为原理介绍,完整代码如下:
package main
import (
"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())
}
//dailContext := func(ctx context.Context, network, addr string) (net.Conn, error) {}
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() {
// for testing
config.method = "aes-256-cfb" // default method
config.password = "your socks pw"
config.port = 0 // your port
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
网络请求流程,可以在应用代码中拿来访问被墙的网站、做加密传输等等。
app -socks5-encryption-data-> Socks5Server -HTTP-> Server
事实上,利用上述方法和结合上一篇文章为Redis编写安全通道,可以让数据库走socks5
代理。有空详细写写。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK