帮助中心

汇集使用技巧,一分钟上手动态IP,赢在大数据时代,从这里开始。

当前位置:帮助中心>最新资讯

HTTP隧道代理原理和实现

  之前我们介绍了HTTP普通代理的原理和golang简单实现,也提到过普通代理的局限,比如无法代理HTTPS的报文,此外普通代理也只能代理HTTP协议报文,无法支持其他协议的数据转发。当然有问题就有解决方案,对于这些问题HTTP可以通过隧道(tunnel)代理来解决。

  隧道代理的原因也可以用一句话来总结:

  代理服务器和真正的服务器之间建立起TCP连接,然后在客户端和真正服务器端进行数据的直接转发。

  《HTTP权威指南》对隧道描述的原话是:

  The CONNECT method asks a tunnel gateway to create a TCP connection to an arbitrary destination server and port and to blindly relay subsequent data between client and server.

  意思和我之前的那句话一样,只不过有了更多的细节说明。

  下图是《HTTP权威指南》书中的插图,它讲解了客户端通过隧道代理连接HTTPS服务器的过程。

  (a)客户端先发送CONNECT请求到隧道代理服务器,告诉它建立和服务器的TCP连接(因为是TCP连接,只需要ip和端口就行,不需要关注上层的协议类型)

  (b,c)代理服务器成功和后端服务器建立TCP连接

  (d)代理服务器返回HTTP 200 Connection Established报文,告诉客户端连接已经成功建立

  (e)这个时候就建立起了连接,所有发给代理的TCP报文都会直接转发,从而实现服务器和客户端的通信

HTTP隧道代理原理和实现

  CONNECT请求

  CONNECT请求的内容和其他HTTP方法的语法一样,只不过它在状态栏(status line)指定了真正服务器的地址。请求URI替换成了hostname和port的字符串,比如:

  CONNECT realserver.com:443 HTTP/1.0

  User-Agent:GoProxy

  而其他HTTP请求的状态栏对应位置是路径地址,比如:

  GET/about HTTP/1.0

  User-Agent:GoProxy

  知道了hostname和port,代理服务器就能正确地建立,才能够继续后面的访问。需要注意的是,客户端应该尽量少地暴露其他信息,最好只有状态栏一行的内容,因为CONNECT请求是没有经过加密的。如果想通过这种方式进行HTTPS安全访问,那么不要在CONNECT请求中暴露敏感数据(比如cookie)是必须的。

  如果代理服务器正确接受了CONNECT请求,并且成功建立了和后端服务器的TCP连接,它应该返回200状态码的应答,按照大多数的约定为200 Connection Establised。应答也不需要包含其他的头部和body,因为后续的数据传输都是直接转发的,代理不会分析其中的内容。

  代码实现

  有了上面的理论分析,我们可以写出下面的代码:

  package main

  import(

  "fmt"

  "io"

  "net"

  "net/http"

  )

  type Pxy struct{}

  func NewProxy()*Pxy{

  return&Pxy{}

  }

  //ServeHTTP is the main handler for all requests.

  func(p*Pxy)ServeHTTP(rw http.ResponseWriter,req*http.Request){

  fmt.Printf("Received request%s%s%sn",

  req.Method,

  req.Host,

  req.RemoteAddr,

  )

  if req.Method!="CONNECT"{

  rw.WriteHeader(http.StatusMethodNotAllowed)

  rw.Write([]byte("This is a http tunnel proxy,only CONNECT method is allowed."))

  return

  }

  //Step 1

  host:=req.URL.Host

  hij,ok:=rw.(http.Hijacker)

  if!ok{

  panic("HTTP Server does not support hijacking")

  }

  client,_,err:=hij.Hijack()

  if err!=nil{

  return

  }

  //Step 2

  server,err:=net.Dial("tcp",host)

  if err!=nil{

  return

  }

  client.Write([]byte("HTTP/1.0 200 Connection Establishedrnrn"))

  //Step 3

  go io.Copy(server,client)

  io.Copy(client,server)

  }

  func main(){

  proxy:=NewProxy()

  http.ListenAndServe("0.0.0.0:8080",proxy

  }

  这段代码和前一篇文章的大致框架相同,但是处理的逻辑有些许的区别。

  首先接收到客户端的请求之后,代理服务器需要获得底层的TCP连接,这样才能转发数据,所以这里看到了Hijacker类型转换和Hijack()调用,它们最终的目的是拿到客户端的TCP连接(net.TCPConn)

  然后代理服务器调用net.Dial函数和真正的服务器端建立TCP连接,并在成功后返回给客户端200应答

  最后就是在客户端和服务器端直接转发数据

  NOTE:默认的ServeMux不支持CONNECT方法的请求,请直接使用自己编写的Proxy作为Mux。

  稍加修改我们就能实现同时支持两种代理的代码,我已经把最终的代码放到了github上面。



在线咨询
微信号

微信号

回到顶部