在日常网络使用中,web代理经常被使用,这篇文章将介绍http代理的基本原理以及简单的go实现。
http代理有两种形式,普通代理和隧道代理。
普通代理
根据《HTTP 权威指南》,在普通代理中,代理作为中间人既是客户端也是服务端。代理服务器会自动提取请求数据包的HTTP请求数据,并且把HTTP相应数据转发给发送请求的客户端。
利用Go的http
库,我们可以实现一个最简单的http代理:
package main
import (
"io"
"log"
"net/http"
)
func handleHTTP(w http.ResponseWriter, req *http.Request) {
// 发送请求到目标服务器
client := &http.Client{}
log.Printf("req.URL: %s", req.URL.String())
request, err := http.NewRequest(req.Method, req.URL.String(), req.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 复制请求头
copyHeader(req.Header, request.Header)
response, err := client.Do(request)
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
defer response.Body.Close()
// 复制响应头
copyHeader(response.Header, w.Header())
w.WriteHeader(response.StatusCode)
io.Copy(w, response.Body)
}
func copyHeader(src, dst http.Header) {
for k, vv := range src {
for _, v := range vv {
dst.Add(k, v)
}
}
}
func main() {
server := &http.Server{
Addr: ":8080",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
handleHTTP(w, r)
}),
}
log.Fatal(server.ListenAndServe())
}
执行该代码即可在本地的8080
端口开启http代理,将应用程序的代理地址修改为http://127.0.0.1:8080
可以正常工作。
但目前这种方式实现的代理会无法访问https网站,为了实现对https的支持,需要下面的隧道道理。
隧道代理
隧道代理可以实现https的支持,其基本原理是在代理服务器和真正的服务器之间建立起 TCP 连接,然后在客户端和真正服务器端进行数据的直接的转发,不做任何修改。
当通过通过代理访问服务端时,应用首先通过 CONNECT 请求,让代理创建一条到服务端的 TCP 连接,而一旦 TCP 连接建好,代理后续会无脑转发流量。因此这种代理,实际上可以用于任意基于 TCP 的应用层协议。
接下来让我们用Go实现隧道代理:
package main
import (
"io"
"log"
"net"
"net/http"
)
func handleTunneling(w http.ResponseWriter, req *http.Request) {
// 目标地址
log.Println("get request: ", req.URL.Host)
destConn, err := net.Dial("tcp", req.URL.Host)
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
// 响应客户端
w.WriteHeader(http.StatusOK)
hijacker, ok := w.(http.Hijacker)
if !ok {
http.Error(w, "Hijacking not supported", http.StatusInternalServerError)
return
}
clientConn, _, err := hijacker.Hijack()
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
// 数据转发
go transfer(destConn, clientConn)
go transfer(clientConn, destConn)
}
func transfer(destination io.WriteCloser, source io.ReadCloser) {
defer destination.Close()
defer source.Close()
io.Copy(destination, source)
}
func main() {
// 设置 HTTP 服务器
server := &http.Server{
Addr: ":8080", // 监听端口
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodConnect {
handleTunneling(w, r)
} else {
w.WriteHeader(http.StatusMethodNotAllowed)
}
}),
}
log.Fatal(server.ListenAndServe())
}
以上代码运行后,会在本地 8080
端口开启 HTTP 代理服务,修改浏览器的 HTTP 代理为 http://127.0.0.1:8080
后再访问 HTTPS 网站,代理可以正常工作。
合并上面两种形式的代理,就可以得到一个全能的代理服务器,代码如下:
package main
import (
"io"
"log"
"net"
"net/http"
)
func handleTunneling(w http.ResponseWriter, req *http.Request) {
// 目标地址
log.Println("get request: ", req.URL.Host)
destConn, err := net.Dial("tcp", req.URL.Host)
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
// 响应客户端
w.WriteHeader(http.StatusOK)
hijacker, ok := w.(http.Hijacker)
if !ok {
http.Error(w, "Hijacking not supported", http.StatusInternalServerError)
return
}
clientConn, _, err := hijacker.Hijack()
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
// 数据转发
go transfer(destConn, clientConn)
go transfer(clientConn, destConn)
}
func transfer(destination io.WriteCloser, source io.ReadCloser) {
defer destination.Close()
defer source.Close()
io.Copy(destination, source)
}
func handleHTTP(w http.ResponseWriter, req *http.Request) {
// 发送请求到目标服务器
client := &http.Client{}
request, err := http.NewRequest(req.Method, req.URL.String(), req.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 复制请求头
copyHeader(req.Header, request.Header)
response, err := client.Do(request)
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
defer response.Body.Close()
// 复制响应头
copyHeader(response.Header, w.Header())
w.WriteHeader(response.StatusCode)
io.Copy(w, response.Body)
}
func copyHeader(src, dst http.Header) {
for k, vv := range src {
for _, v := range vv {
dst.Add(k, v)
}
}
}
func main() {
server := &http.Server{
Addr: ":8080",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodConnect {
handleTunneling(w, r)
} else {
handleHTTP(w, r)
}
}),
}
log.Fatal(server.ListenAndServe())
}
Comments 1 条评论
博主 全沾攻城狮
这是一条私密评论