编程学习

http代理的介绍与go实现

在日常网络使用中,web代理经常被使用,这篇文章将介绍http代理的基本原理以及简单的go实现。

http代理有两种形式,普通代理和隧道代理。

普通代理#

根据《HTTP 权威指南》,在普通代理中,代理作为中间人既是客户端也是服务端。代理服务器会自动提取请求数据包的HTTP请求数据,并且把HTTP相应数据转发给发送请求的客户端。

image.png

利用Go的http库,我们可以实现一个最简单的http代理:

GO
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 连接,然后在客户端和真正服务器端进行数据的直接的转发,不做任何修改。

image.png

当通过通过代理访问服务端时,应用首先通过 CONNECT 请求,让代理创建一条到服务端的 TCP 连接,而一旦 TCP 连接建好,代理后续会无脑转发流量。因此这种代理,实际上可以用于任意基于 TCP 的应用层协议。

接下来让我们用Go实现隧道代理:

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 网站,代理可以正常工作。

合并上面两种形式的代理,就可以得到一个全能的代理服务器,代码如下:

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 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 0 条公开评论

暂无公开历史评论。

正在连接评论服务… / Connecting to comments…

    搜索内容

    最近发布的内容