一個(gè)簡(jiǎn)單的Golang實(shí)現(xiàn)的HTTP Proxy方法
最近因?yàn)閾Q了Mac,以前的Linux基本上不再使用了,但是我的SS代理還得用。SS代理大家都了解,一個(gè)很NB的socks代理工具,但是就是因?yàn)樗荢ocks的,想用HTTP代理的時(shí)候很不方便。
以前在Linux下的時(shí)候,會(huì)安裝一個(gè)Privoxy把socks代理轉(zhuǎn)換為HTTP代理,開(kāi)機(jī)啟動(dòng),也比較方便。但是Mac下使用Brew安裝的Privoxy就很難用,再加上以前一個(gè)有個(gè)想法,一個(gè)軟件搞定socks和HTTP代理,這樣就不用安裝一個(gè)單獨(dú)的軟件做轉(zhuǎn)換了。
想著就開(kāi)始做吧,以前基本上沒(méi)有搞過(guò)太多的網(wǎng)絡(luò)編程,最近也正好在研究Go,正好練練手。
我們這里主要講使用HTTP/1.1協(xié)議中的CONNECT方法建立起來(lái)的隧道連接,實(shí)現(xiàn)的HTTP Proxy。這種代理的好處就是不用知道客戶(hù)端請(qǐng)求的數(shù)據(jù),只需要原封不動(dòng)的轉(zhuǎn)發(fā)就可以了,對(duì)于處理HTTPS的請(qǐng)求就非常方便了,不用解析他的內(nèi)容,就可以實(shí)現(xiàn)代理。
啟動(dòng)代理監(jiān)聽(tīng)
要想做一個(gè)HTTP Proxy,我們需要啟動(dòng)一個(gè)服務(wù)器,監(jiān)聽(tīng)一個(gè)端口,用于接收客戶(hù)端的請(qǐng)求。Golang給我們提供了強(qiáng)大的net包供我們使用,我們啟動(dòng)一個(gè)代理服務(wù)器監(jiān)聽(tīng)非常方便。
l, err := net.Listen("tcp", ":8080") if err != nil { log.Panic(err) }
以上代理我們就實(shí)現(xiàn)了一個(gè)在8080端口上監(jiān)聽(tīng)的服務(wù)器,我們這里沒(méi)有寫(xiě)ip地址,默認(rèn)在所有ip地址上進(jìn)行監(jiān)聽(tīng)。如果你只想本機(jī)適用,可以使用127.0.0.1:8080,這樣機(jī)器就訪(fǎng)問(wèn)不了你的代理服務(wù)器了。
監(jiān)聽(tīng)接收代理請(qǐng)求
啟動(dòng)了代理服務(wù)器,就可以開(kāi)始接受不了代理請(qǐng)求了,有了請(qǐng)求,我們才能做進(jìn)一步的處理。
for { client, err := l.Accept() if err != nil { log.Panic(err) } go handleClientRequest(client) }
Listener接口的Accept方法,會(huì)接受客戶(hù)端發(fā)來(lái)的連接數(shù)據(jù),這是一個(gè)阻塞型的方法,如果客戶(hù)端沒(méi)有連接數(shù)據(jù)發(fā)來(lái),他就是阻塞等待。接收來(lái)的連接數(shù)據(jù),會(huì)馬上交給handleClientRequest方法進(jìn)行處理,這里使用一個(gè)go關(guān)鍵字開(kāi)一個(gè)goroutine的目的是不阻塞客戶(hù)端的接收,代理服務(wù)器可以馬上接收下一個(gè)連接請(qǐng)求。
解析請(qǐng)求,獲取要訪(fǎng)問(wèn)的IP和端口
有了客戶(hù)端的代理請(qǐng)求了,我們還得從請(qǐng)求里提取客戶(hù)端要訪(fǎng)問(wèn)的遠(yuǎn)程主機(jī)的IP和端口,這樣我們的代理服務(wù)器才可以建立和遠(yuǎn)程主機(jī)的連接,代理轉(zhuǎn)發(fā)。
HTTP協(xié)議的頭信息里就包含有我們需要的主機(jī)名(IP)和端口信息,并且是明文的,協(xié)議很規(guī)范,類(lèi)似于:
CONNECT www.google.com:443 HTTP/1.1 Host: www.google.com:443 Proxy-Connection: keep-alive User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36
可以看到我們需要的在第一行,第一個(gè)行的信息以空格分開(kāi),第一部分CONNECT是請(qǐng)求方法,這里是CONNECT,除此之外還有GET,POST等,都是HTTP協(xié)議的標(biāo)準(zhǔn)方法。
第二部分是URL,https的請(qǐng)求只有host和port,http的請(qǐng)求是一個(gè)完成的url,等下會(huì)看個(gè)樣例,就明白了。
第三部是HTTP的協(xié)議和版本,這個(gè)我們不用太關(guān)注。
以上是一個(gè)https的請(qǐng)求,我們看下http的:
GET http://www.flysnow.org/ HTTP/1.1 Host: www.flysnow.org Proxy-Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36
可以看到htt的,沒(méi)有端口號(hào)(默認(rèn)是80);比https多了schame–http://。
有了分析,下面我們就可以從HTTP頭信息中獲取請(qǐng)求的url和method信息了。
var b [1024]byte n, err := client.Read(b[:]) if err != nil { log.Println(err) return } var method, host, address string fmt.Sscanf(string(b[:bytes.IndexByte(b[:], '\n')]), "%s%s", &method, &host) hostPortURL, err := url.Parse(host) if err != nil { log.Println(err) return }
然后需要進(jìn)一步對(duì)url進(jìn)行解析,獲取我們需要的遠(yuǎn)程服務(wù)器信息
if hostPortURL.Opaque == "443" { //https訪(fǎng)問(wèn) address = hostPortURL.Scheme + ":443" } else { //http訪(fǎng)問(wèn) if strings.Index(hostPortURL.Host, ":") == -1 { //host不帶端口, 默認(rèn)80 address = hostPortURL.Host + ":80" } else { address = hostPortURL.Host } }
這樣就完整了獲取了要請(qǐng)求服務(wù)器的信息,他們可能是以下幾種格式
ip:port hostname:port domainname:port
就是有可能是ip(v4orv6),有可能是主機(jī)名(內(nèi)網(wǎng)),有可能是域名(dns解析)
代理服務(wù)器和遠(yuǎn)程服務(wù)器建立連接
有了遠(yuǎn)程服務(wù)器的信息了,就可以進(jìn)行撥號(hào)建立連接了,有了連接,才可以通信。
//獲得了請(qǐng)求的host和port,就開(kāi)始撥號(hào)吧 server, err := net.Dial("tcp", address) if err != nil { log.Println(err) return }
數(shù)據(jù)轉(zhuǎn)發(fā)
撥號(hào)成功后,就可以進(jìn)行數(shù)據(jù)代理傳輸了
if method == "CONNECT" { fmt.Fprint(client, "HTTP/1.1 200 Connection established\r\n") } else { server.Write(b[:n]) } //進(jìn)行轉(zhuǎn)發(fā) go io.Copy(server, client) io.Copy(client, server)
其中對(duì)CONNECT方法有單獨(dú)的回應(yīng),客戶(hù)端說(shuō)要建立連接,代理服務(wù)器要回應(yīng)建立好了,然后才可以像HTTP一樣請(qǐng)求訪(fǎng)問(wèn)。
運(yùn)行外國(guó)外VPS上
到這里,我們的代理服務(wù)器全部開(kāi)發(fā)完成了,下面是完整的源代碼:
package main import ( "bytes" "fmt" "io" "log" "net" "net/url" "strings" ) func main() { log.SetFlags(log.LstdFlags|log.Lshortfile) l, err := net.Listen("tcp", ":8081") if err != nil { log.Panic(err) } for { client, err := l.Accept() if err != nil { log.Panic(err) } go handleClientRequest(client) } } func handleClientRequest(client net.Conn) { if client == nil { return } defer client.Close() var b [1024]byte n, err := client.Read(b[:]) if err != nil { log.Println(err) return } var method, host, address string fmt.Sscanf(string(b[:bytes.IndexByte(b[:], '\n')]), "%s%s", &method, &host) hostPortURL, err := url.Parse(host) if err != nil { log.Println(err) return } if hostPortURL.Opaque == "443" { //https訪(fǎng)問(wèn) address = hostPortURL.Scheme + ":443" } else { //http訪(fǎng)問(wèn) if strings.Index(hostPortURL.Host, ":") == -1 { //host不帶端口, 默認(rèn)80 address = hostPortURL.Host + ":80" } else { address = hostPortURL.Host } } //獲得了請(qǐng)求的host和port,就開(kāi)始撥號(hào)吧 server, err := net.Dial("tcp", address) if err != nil { log.Println(err) return } if method == "CONNECT" { fmt.Fprint(client, "HTTP/1.1 200 Connection established\r\n") } else { server.Write(b[:n]) } //進(jìn)行轉(zhuǎn)發(fā) go io.Copy(server, client) io.Copy(client, server) }
把源代碼編譯,然后放到你國(guó)外的VPS上,在自己機(jī)器上配置好HTTP代理,就可以到處訪(fǎng)問(wèn),自由自在了。
以上這篇一個(gè)簡(jiǎn)單的Golang實(shí)現(xiàn)的HTTP Proxy方法就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Golang庫(kù)插件注冊(cè)加載機(jī)制的問(wèn)題
這篇文章主要介紹了Golang庫(kù)插件注冊(cè)加載機(jī)制,這里說(shuō)的插件并不是指的golang原生的可以在buildmode中加載指定so文件的那種加載機(jī)制,需要的朋友可以參考下2022-03-03GoLang并發(fā)機(jī)制探究goroutine原理詳細(xì)講解
goroutine是Go語(yǔ)言提供的語(yǔ)言級(jí)別的輕量級(jí)線(xiàn)程,在我們需要使用并發(fā)時(shí),我們只需要通過(guò) go 關(guān)鍵字來(lái)開(kāi)啟 goroutine 即可。這篇文章主要介紹了GoLang并發(fā)機(jī)制goroutine原理,感興趣的可以了解一下2022-12-12Go語(yǔ)言defer與return執(zhí)行的先后順序詳解
這篇文章主要為大家介紹了Go語(yǔ)言defer與return執(zhí)行的先后順序詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12go并發(fā)實(shí)現(xiàn)素?cái)?shù)篩的代碼
這篇文章主要介紹了go并發(fā)實(shí)現(xiàn)素?cái)?shù)篩的代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03Go方法簡(jiǎn)單性和高效性的充分體現(xiàn)詳解
本文深入探討了Go語(yǔ)言中方法的各個(gè)方面,包括基礎(chǔ)概念、定義與聲明、特性、實(shí)戰(zhàn)應(yīng)用以及性能考量,文章充滿(mǎn)技術(shù)深度,通過(guò)實(shí)例和代碼演示,力圖幫助讀者全面理解Go方法的設(shè)計(jì)哲學(xué)和最佳實(shí)踐2023-10-10