Go实现Https代理服务

作者:Zarten知乎专栏:Go开发深入详解知乎ID: Zarten简介:互联网一线工作者,尊重原创并欢迎评论留言指出不足之处,也希望多些关注和点赞是给作者最好的鼓励 !

概述

http(s)代理在日常使用中比较常用,所以这里写了一个https代理服务器,只需另外一台机器就可搭建自己的代理服务器了。

此服务器使用Go语言编写,全部使用go语言标准库,没有使用任何第三方库,编译后可以直接使用。下面将从代码底层讲述如何实现一个https代理服务器。

实现代码

此项目主要包含一个main函数和两个包,两个包包括一个https服务和一个proxy服务。

main函数

main函数是程序的启动入口,启动时需要传入https服务器的监听地址和端口,代码如下

func main(){ var listenAdress string flag.StringVar(&listenAdress, "L", "0.0.0.0:8080", "listen address.eg: 127.0.0.1:8080") flag.Parse() if !checkAdress(listenAdress){ logger.Fatal("-L listen address format incorrect.Please check it") } httpsserve.Serve(listenAdress) }

使用-L参数指定监听地址和端口,若没有指定,默认为“0.0.0.0:8080”,同时会通过checkAdress函数检查地址的合法性。

最后调用httpsserve包中的Serve函数启动https服务。

https服务

https服务包含了http和https协议的服务,因为包含https,所以需要证书,在这里会自动生成证书。代码如下

func Serve(listenAdress string){ cert, err := genCertificate() if err != nil { logger.Fatal(err) } server := &http.Server{ Addr: listenAdress, TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert},}, Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { proxy.Serve(w, r) }), } logger.Fatal(server.ListenAndServe()) }

https服务器使用标准库中的net/http库,使用TLSConfig参数来配置证书信息,在Handler参数中调用proxy服务。

下面将重点讲解此项目的核心proxy服务。

proxy服务

此服务也是项目的核心所处,下面将重点讲解。

由于代理的使用者可能请求的网站是https或http的,所以这2种情况要分别处理。若请求的是https,实际上会先发送一个CONNECT请求。代码如下

func Serve(w http.ResponseWriter, r *http.Request){ if r.Method == http.MethodConnect { handleHttps(w, r) } else { handleHttp(w, r) } }

handleHttps

若检测到是CONNECT请求,则会经过此函数处理。

此函数首先会使用函数net.DialTimeout根据客户端请求的目标网址来进行一次请求并获得连接信息,并回复一个200状态码给客户端。之后会使用Hijack来接管http服务来进行通信转发。代码如下

func handleHttps(w http.ResponseWriter, r *http.Request){ destConn, err := net.DialTimeout("tcp", r.Host, 60*time.Second) 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) } go transfer(destConn, clientConn) go transfer(clientConn, destConn) }

函数transfer会进行转发操作,内部调用了io.Copy来进行转发。

handleHttp

此函数处理http请求的情况。

首先会根据目标地址调用标准库中的http.DefaultTransport.RoundTrip函数进行请求,RoundTrip函数会保持原样请求,并不能修改一些细节包括重定向、认证、cookie等。

最后需要将回复的http头信息复制后回复给客户端。代码如下

func handleHttp(w http.ResponseWriter, r *http.Request){ resp, err := http.DefaultTransport.RoundTrip(r) if err != nil { http.Error(w, err.Error(), http.StatusServiceUnavailable) return } defer resp.Body.Close() copyHeader(w.Header(), resp.Header) w.WriteHeader(resp.StatusCode) io.Copy(w, resp.Body) }

总结

全部源码已上传至Github,地址:Zartenc/httpsproxy

上面是代码的主要部分,可以看到代理的核心思想就是做信息的转发。

另外若代理搭建在国外服务器,在国内访问某些网站时仍然会被墙,这是因为请求的网站信息已经在电信部门监听中,固无法访问。无法访问的限制原理可以看这篇文章,突破限制的主要思想是隐藏请求信息,目前shadowscoks协议在这方面做的比较出色。