一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情。
本文的目标是实现一个代理服务:
- 代理各种HTTP 请求以及HTTPS 请求
- 支持长连接
代理服务器在网络中类似于NAT协议,不过它是基于软件的。
此处具体的实现思路如下:
- 通过监听在一个端口上,处理每个传入的连接。
- 对于每个连接,启动一个goroutine处理。
- 读取相关数据(根据得到的代理头部,获取目标主机的信息),与目标网站建立连接,然后通过不断从源主机复制数据到目标,并将目标主机的发送的数据复制回到源主机,不断复制直到断开连接,实现数据的代理中继。
具体到对于数据的处理,需要根据请求类型来处理,具体来说如下:
-
对于HTTP代理,一般来说可以只关注第1行,使用代理时会发送一个代理投入,当为HTTP请求时,有代理可以直接解析出第1行,特别指出的完整host主机的地址,然后向该地址发出链接。使用两个程序分别往方向进行复制。值得注意的是,这里需要先前读取了的那一部分,请代理请求头也应该写到服务器中。
-
对于另一种请求,也就是https请求。出于加密的目的,它会有一个额外的HTTP请求头,用于让代理服务器识别代理方式为HTTPS,以及请求的目标域名,这个时候他的请求方法是CONNECT,同样的在这里需要向请求头中指定的那个完整的地址发起连接。不一样的是这个代理头部,不再需要发送给远程服务器,而是需要我们对来源主机进行响应,表示已经建立连接。
主要逻辑1:
func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
server, err := net.Listen("tcp", ":8081")
if err != nil {
log.Panic(err)
}
for {
client, err := server.Accept()
if err != nil {
log.Panic(err)
}
log.Println(client.RemoteAddr().String())
go handleClientRequest(client)
}
}
主要逻辑2: 选择处理方式
主要逻辑3: 利用IO.Copy() 启动两个协程复制传输。
最终的效果,可以使用CURL 进行,特别地, CURL 还可以同时请求多个参数以测试 HTTP 1.1 的多路复用机制。
在这里有一个很有意思地点在于,我们没有做任何显式地维持处理长连接。但是它是支持保持连接的。因为我们仅仅是维护一个连接,并不断的进行数据读取。如果是长链接那么他就会一直保持读取状态,作为 goroutine被不断的调度执行。这样只要一直处于维持双向的复制状态,那么它就相当于是维持了一个长连接。天然的支持了HTTP/1.1的长连接。