http代理的介绍与go实现

发布于 2024-01-18  75 次阅读


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

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

普通代理

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

image.png

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

image.png

当通过通过代理访问服务端时,应用首先通过 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())
}