本文详细介绍了Golang 实现 http 代理的实现,在实际业务中有需求的同学可以学起来了!
代理是网络中的一项重要的功能,其功能就是代理网络用户去取得网络信息。形象的说:它是网络信息的中转站,对于客户端来说,代理扮演的是服务器的角色,接收请求报文,返回响应报文;对于 web 服务器来说,代理扮演的是客户端的角色,发送请求报文,接收响应报文。
代理具有多种类型,如果是根据网络用户划分的话,可以划分为正向代理和反向代理:
正向代理:将客户端作为网络用户。客户端访问服务端时,先访问代理服务器,随后代理服务器再访问服务端。此过程需客户端进行代理配置,对服务端透明。
反向代理:将服务端作为网络用户。访问过程与正向代理相同,不过此过程对客户端透明,需服务端进行代理配置(也可不配置)。
针对正向代理和反向代理,分别有不同的代理协议,即代理服务器和网络用户之间通信所使用的协议:
正向代理:
http
https
socks4
socks5
vpn:就功能而言,vpn 也可以被认为是代理
反向代理:
tcp
udp
http
https
接下来我们就说说 http 代理。
http 代理概述
http 代理是正向代理中较为简单的代理方式,它使用 http 协议作为客户端和代理服务器的传输协议。
http 代理可以承载 http 协议,https 协议,ftp 协议等等。对于不同的协议,客户端和代理服务器间的数据格式略有不同。
http 协议
我们先来看看 http 协议下客户端发送给代理服务器的 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 协议的过时的代理服务器。HTTP/1.1 才开始有长连接功能,直接连接的情况下,客户端发送的 HTTP Header 中如果有Connection: keep-alive字段,表示使用长连接和服务端进行 http 通信,但如果中间有过时的代理服务器,该代理服务器将无法与客户端和服务端进行长连接,造成客户端和服务端一直等待,白白浪费时间。
因此使用Proxy-Connection字段代替Connection字段,如果代理服务器使用 HTTP/1.1 协议,能够识别Proxy-Connection字段,则将该字段转换成Connection再发送给服务端;如果不能识别,直接发送给服务端,因为服务端也无法识别,则使用短连接进行通信。
http 代理 http 协议交互过程如图:
http 代理 http 协议
https 协议
接下来我们来看看 https 协议下,客户端发送给代理服务器的 HTTP Header:
CONNECTstaight.github.io:443HTTP/1.1 Host:staight.github.io:443 Proxy-Connection:keep-alive
如上,https 协议和 http 协议相比:
请求方法从GET变成CONNECT
url 没有 protocol 字段
实际上,由于 https 下客户端和服务端的通信除了开头的协商以外都是密文,中间的代理服务器不再承担修改 http 报文再转发的功能,而是一开始就和客户端协商好服务端的地址,随后的 tcp 密文直接转发即可。
http 代理 https 协议交互过程如图:
代码实现
首先,创建 tcp 服务,并且对于每个 tcp 请求,均调用 handle 函数:
//tcp连接,监听8080端口 l,err:=net.Listen("tcp",":8080") iferr!=nil{ log.Panic(err) } //死循环,每当遇到连接时,调用handle for{ client,err:=l.Accept() iferr!=nil{ log.Panic(err) } gohandle(client) }
然后将获取的数据放入缓冲区:
//用来存放客户端数据的缓冲区 varb[1024]byte //从客户端获取数据 n,err:=client.Read(b[:]) iferr!=nil{ log.Println(err) return }
从缓冲区读取 HTTP 请求方法,URL 等信息:
varmethod,URL,addressstring //从客户端数据读入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 协议和 https 协议获取地址的方式不同,分别处理:
//如果方法是CONNECT,则为https协议 ifmethod=="CONNECT"{ address=hostPortURL.Scheme+":"+hostPortURL.Opaque }else{//否则为http协议 address=hostPortURL.Host //如果host不带端口,则默认为80 ifstrings.Index(hostPortURL.Host,":")==-1{//host不带端口,默认80 address=hostPortURL.Host+":80" } }
用获取到的地址向服务端发起请求。如果是 http 协议,将客户端的请求直接转发给服务端;如果是 https 协议,发送 http 响应:
//获得了请求的host和port,向服务端发起tcp连接 server,err:=net.Dial("tcp",address) iferr!=nil{ log.Println(err) return } //如果使用https协议,需先向客户端表示连接建立完毕 ifmethod=="CONNECT"{ fmt.Fprint(client,"HTTP/1.1200Connectionestablished ") }else{//如果使用http协议,需将从客户端得到的http请求转发给服务端 server.Write(b[:n]) }
最后,将所有客户端的请求转发至服务端,将所有服务端的响应转发给客户端:
//将客户端的请求转发至服务端,将服务端的响应转发给客户端。io.Copy 为阻塞函数,文件描述符不关闭就不停止 goio.Copy(server,client) io.Copy(client,server
完整的源代码:
packagemain import( "bytes" "fmt" "io" "log" "net" "net/url" "strings" ) funcmain(){ //tcp连接,监听8080端口 l,err:=net.Listen("tcp",":8080") iferr!=nil{ log.Panic(err) } //死循环,每当遇到连接时,调用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()) //用来存放客户端数据的缓冲区 varb[1024]byte //从客户端获取数据 n,err:=client.Read(b[:]) iferr!=nil{ log.Println(err) return } varmethod,URL,addressstring //从客户端数据读入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协议 ifmethod=="CONNECT"{ address=hostPortURL.Scheme+":"+hostPortURL.Opaque }else{//否则为http协议 address=hostPortURL.Host //如果host不带端口,则默认为80 ifstrings.Index(hostPortURL.Host,":")==-1{//host不带端口,默认80 address=hostPortURL.Host+":80" } } //获得了请求的host和port,向服务端发起tcp连接 server,err:=net.Dial("tcp",address) iferr!=nil{ log.Println(err) return } //如果使用https协议,需先向客户端表示连接建立完毕 ifmethod=="CONNECT"{ fmt.Fprint(client,"HTTP/1.1200Connectionestablished ") }else{//如果使用http协议,需将从客户端得到的http请求转发给服务端 server.Write(b[:n]) } //将客户端的请求转发至服务端,将服务端的响应转发给客户端。io.Copy 为阻塞函数,文件描述符不关闭就不停止 goio.Copy(server,client) io.Copy(client,server) }
添加代理,然后运行:
-
Web
+关注
关注
2文章
1255浏览量
69322 -
服务器
+关注
关注
12文章
9010浏览量
85160 -
网络
+关注
关注
14文章
7511浏览量
88605 -
HTTP
+关注
关注
0文章
501浏览量
31047 -
客户端
+关注
关注
1文章
289浏览量
16659
原文标题:Golang 实现一个简单的 http 代理
文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论