本文詳細介紹了Golang 實現(xiàn) http 代理的實現(xiàn),在實際業(yè)務中有需求的同學可以學起來了!
代理是網(wǎng)絡中的一項重要的功能,其功能就是代理網(wǎng)絡用戶去取得網(wǎng)絡信息。形象的說:它是網(wǎng)絡信息的中轉(zhuǎn)站,對于客戶端來說,代理扮演的是服務器的角色,接收請求報文,返回響應報文;對于 web 服務器來說,代理扮演的是客戶端的角色,發(fā)送請求報文,接收響應報文。
代理具有多種類型,如果是根據(jù)網(wǎng)絡用戶劃分的話,可以劃分為正向代理和反向代理:
正向代理:將客戶端作為網(wǎng)絡用戶??蛻舳嗽L問服務端時,先訪問代理服務器,隨后代理服務器再訪問服務端。此過程需客戶端進行代理配置,對服務端透明。
反向代理:將服務端作為網(wǎng)絡用戶。訪問過程與正向代理相同,不過此過程對客戶端透明,需服務端進行代理配置(也可不配置)。
針對正向代理和反向代理,分別有不同的代理協(xié)議,即代理服務器和網(wǎng)絡用戶之間通信所使用的協(xié)議:
正向代理:
http
https
socks4
socks5
vpn:就功能而言,vpn 也可以被認為是代理
反向代理:
tcp
udp
http
https
接下來我們就說說 http 代理。
http 代理概述
http 代理是正向代理中較為簡單的代理方式,它使用 http 協(xié)議作為客戶端和代理服務器的傳輸協(xié)議。
http 代理可以承載 http 協(xié)議,https 協(xié)議,ftp 協(xié)議等等。對于不同的協(xié)議,客戶端和代理服務器間的數(shù)據(jù)格式略有不同。
http 協(xié)議
我們先來看看 http 協(xié)議下客戶端發(fā)送給代理服務器的 HTTP Header:
//直接連接 GET/HTTP/1.1 Host:staight.github.io Connection:keep-alive //http代理 GEThttp://staight.github.io/HTTP/1.1 Host:staight.github.io Proxy-Connection:keep-alive
可以看到,http 代理比起直接連接:
url 變成完整路徑,/->http://staight.github.io/
Connection字段變成Proxy-Connection字段
其余保持原樣
為什么使用完整路徑?
為了識別目標服務器。如果沒有完整路徑,且沒有 Host 字段的話,代理服務器將無法得知目標服務器的地址。
為什么使用 Proxy-Connection 字段代替 Connection 字段?
為了兼容使用 HTTP/1.0 協(xié)議的過時的代理服務器。HTTP/1.1 才開始有長連接功能,直接連接的情況下,客戶端發(fā)送的 HTTP Header 中如果有Connection: keep-alive字段,表示使用長連接和服務端進行 http 通信,但如果中間有過時的代理服務器,該代理服務器將無法與客戶端和服務端進行長連接,造成客戶端和服務端一直等待,白白浪費時間。
因此使用Proxy-Connection字段代替Connection字段,如果代理服務器使用 HTTP/1.1 協(xié)議,能夠識別Proxy-Connection字段,則將該字段轉(zhuǎn)換成Connection再發(fā)送給服務端;如果不能識別,直接發(fā)送給服務端,因為服務端也無法識別,則使用短連接進行通信。
http 代理 http 協(xié)議交互過程如圖:
http 代理 http 協(xié)議
https 協(xié)議
接下來我們來看看 https 協(xié)議下,客戶端發(fā)送給代理服務器的 HTTP Header:
CONNECTstaight.github.io:443HTTP/1.1 Host:staight.github.io:443 Proxy-Connection:keep-alive
如上,https 協(xié)議和 http 協(xié)議相比:
請求方法從GET變成CONNECT
url 沒有 protocol 字段
實際上,由于 https 下客戶端和服務端的通信除了開頭的協(xié)商以外都是密文,中間的代理服務器不再承擔修改 http 報文再轉(zhuǎn)發(fā)的功能,而是一開始就和客戶端協(xié)商好服務端的地址,隨后的 tcp 密文直接轉(zhuǎn)發(fā)即可。
http 代理 https 協(xié)議交互過程如圖:
代碼實現(xiàn)
首先,創(chuàng)建 tcp 服務,并且對于每個 tcp 請求,均調(diào)用 handle 函數(shù):
//tcp連接,監(jiān)聽8080端口 l,err:=net.Listen("tcp",":8080") iferr!=nil{ log.Panic(err) } //死循環(huán),每當遇到連接時,調(diào)用handle for{ client,err:=l.Accept() iferr!=nil{ log.Panic(err) } gohandle(client) }
然后將獲取的數(shù)據(jù)放入緩沖區(qū):
//用來存放客戶端數(shù)據(jù)的緩沖區(qū) varb[1024]byte //從客戶端獲取數(shù)據(jù) n,err:=client.Read(b[:]) iferr!=nil{ log.Println(err) return }
從緩沖區(qū)讀取 HTTP 請求方法,URL 等信息:
varmethod,URL,addressstring //從客戶端數(shù)據(jù)讀入method,url fmt.Sscanf(string(b[:bytes.IndexByte(b[:],' ')]),"%s%s",&method,&URL) hostPortURL,err:=url.Parse(URL) iferr!=nil{ log.Println(err) return }
http 協(xié)議和 https 協(xié)議獲取地址的方式不同,分別處理:
//如果方法是CONNECT,則為https協(xié)議 ifmethod=="CONNECT"{ address=hostPortURL.Scheme+":"+hostPortURL.Opaque }else{//否則為http協(xié)議 address=hostPortURL.Host //如果host不帶端口,則默認為80 ifstrings.Index(hostPortURL.Host,":")==-1{//host不帶端口,默認80 address=hostPortURL.Host+":80" } }
用獲取到的地址向服務端發(fā)起請求。如果是 http 協(xié)議,將客戶端的請求直接轉(zhuǎn)發(fā)給服務端;如果是 https 協(xié)議,發(fā)送 http 響應:
//獲得了請求的host和port,向服務端發(fā)起tcp連接 server,err:=net.Dial("tcp",address) iferr!=nil{ log.Println(err) return } //如果使用https協(xié)議,需先向客戶端表示連接建立完畢 ifmethod=="CONNECT"{ fmt.Fprint(client,"HTTP/1.1200Connectionestablished ") }else{//如果使用http協(xié)議,需將從客戶端得到的http請求轉(zhuǎn)發(fā)給服務端 server.Write(b[:n]) }
最后,將所有客戶端的請求轉(zhuǎn)發(fā)至服務端,將所有服務端的響應轉(zhuǎn)發(fā)給客戶端:
//將客戶端的請求轉(zhuǎn)發(fā)至服務端,將服務端的響應轉(zhuǎn)發(fā)給客戶端。io.Copy 為阻塞函數(shù),文件描述符不關(guān)閉就不停止 goio.Copy(server,client) io.Copy(client,server
完整的源代碼:
packagemain import( "bytes" "fmt" "io" "log" "net" "net/url" "strings" ) funcmain(){ //tcp連接,監(jiān)聽8080端口 l,err:=net.Listen("tcp",":8080") iferr!=nil{ log.Panic(err) } //死循環(huán),每當遇到連接時,調(diào)用handle for{ client,err:=l.Accept() iferr!=nil{ log.Panic(err) } gohandle(client) } } funchandle(clientnet.Conn){ ifclient==nil{ return } deferclient.Close() log.Printf("remoteaddr:%v ",client.RemoteAddr()) //用來存放客戶端數(shù)據(jù)的緩沖區(qū) varb[1024]byte //從客戶端獲取數(shù)據(jù) n,err:=client.Read(b[:]) iferr!=nil{ log.Println(err) return } varmethod,URL,addressstring //從客戶端數(shù)據(jù)讀入method,url fmt.Sscanf(string(b[:bytes.IndexByte(b[:],' ')]),"%s%s",&method,&URL) hostPortURL,err:=url.Parse(URL) iferr!=nil{ log.Println(err) return } //如果方法是CONNECT,則為https協(xié)議 ifmethod=="CONNECT"{ address=hostPortURL.Scheme+":"+hostPortURL.Opaque }else{//否則為http協(xié)議 address=hostPortURL.Host //如果host不帶端口,則默認為80 ifstrings.Index(hostPortURL.Host,":")==-1{//host不帶端口,默認80 address=hostPortURL.Host+":80" } } //獲得了請求的host和port,向服務端發(fā)起tcp連接 server,err:=net.Dial("tcp",address) iferr!=nil{ log.Println(err) return } //如果使用https協(xié)議,需先向客戶端表示連接建立完畢 ifmethod=="CONNECT"{ fmt.Fprint(client,"HTTP/1.1200Connectionestablished ") }else{//如果使用http協(xié)議,需將從客戶端得到的http請求轉(zhuǎn)發(fā)給服務端 server.Write(b[:n]) } //將客戶端的請求轉(zhuǎn)發(fā)至服務端,將服務端的響應轉(zhuǎn)發(fā)給客戶端。io.Copy 為阻塞函數(shù),文件描述符不關(guān)閉就不停止 goio.Copy(server,client) io.Copy(client,server) }
添加代理,然后運行:
-
Web
+關(guān)注
關(guān)注
2文章
1276瀏覽量
70593 -
服務器
+關(guān)注
關(guān)注
12文章
9596瀏覽量
86986 -
網(wǎng)絡
+關(guān)注
關(guān)注
14文章
7713瀏覽量
90162 -
HTTP
+關(guān)注
關(guān)注
0文章
516瀏覽量
32290 -
客戶端
+關(guān)注
關(guān)注
1文章
296瀏覽量
16940
原文標題:Golang 實現(xiàn)一個簡單的 http 代理
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
Golang爬蟲語言接入代理?
如何在Golang中實現(xiàn)反向代理
http代理概述及代碼實現(xiàn)方法
python代碼中使用HTTP代理IP,demo注釋清晰
http代理的作用如下所示
解析Golang定時任務庫gron設計和原理
一個快速應用程序開發(fā)(RAD)工具(Golang版)
Golang配置代理方法

評論