代理那些事

本文只涉及代理的一些技术性原理和术语解释。

我在工作中经常用到代理:比如nginx的反向代理,使用littleproxy来作为程序可控的正向代理,公司内作为正向代理访问外网一些资料。

什么是代理

那么什么是代理,其实看下面的图,很简单,不需要用过多的文字来描述:

用户要访问app,因为各种原因直接不可达,那么在用户和app之间放一层代理:
1. 用户传入的请求流到代理
2. 代理将流转到目标app
3. app的响应流到代理
4. 代理将响应流转给用户
说白了,代理的作用就是你给我什么流,我转就是了。

那么假如让你来写一个代理软件,是不是很简单,只需要一个socket接收,另一个socket转发就可以了?理论上是这样的,比如HAPROXY就是这样的。但是实际上还是有很多地方需要考虑的,更多的是协议!

代理和协议转换

假如代理实现的是http/https的协议,那么用户传入的请求必须是http/https;假如代理实现的是mysql的协议,那么用户传入的请求必须是mysql协议;假如代理实现的是socks的协议,那么用户传入的请求必须是socks协议。

下面以socks代理服务器来举例说明:

等一下,有几个情况先看一下:
1. firefox中设置好了socks代理服务器后,居然能够使得socks代理http协议的流量?也就是说上面图中,发出http协议的请求,居然socks代理能够接受?原因是:firefox事先将http转换成了socks协议然后再发送,同理在响应的时候将socks协议转成了http协议了。
2. curl中,通过 --socks 来访问socks代理,那么curl的收发代码中肯定有对 http 互转 socks 的代码。
3. 在git中,我们可以设置如下来进行git的代理访问:

git config --global http.proxy 'socks5://127.0.0.1:1080'
git config --global https.proxy 'socks5://127.0.0.1:1080'

但是在git中,非常奇怪的能看到 http.proxy 中,居然可以设置 socks的协议。这个不像上2个情况,比如第1个firefox是对http和socks进行分别设置,然后由firefox来转换协议;第2个的curl通过--socks和-x来区分,由curl来转换协议。那么git怎么弄了个在 http 中设置成 socks 的协议呢?
这是因为只有HTTP_PROXY这种约定俗成的环境变量定义,而没有SOCKS_PROXY这个环境变量。git采用了一个取巧的办法(尽管我没有查git的源代码,但是根据原理肯定能猜出来),就是判断HTTP_PROXY的内容假如是socks协议的就转socks。

上面3个情况,都可以通过设置环境变量进行全局代理,详见HTTP_PROXY,HTTPS_PROXY,NO_PROXY,ALL_PROXY来自什么协议?

通过上面3个情况可以看到,对于 http互转socks这个过程,都是在客户端本身内部完成的,而和socks代理没有任何关系。

根据上面的理论,再来看一个现象,分析一下为什么会出现这样的情况:
minikube start
然后会去下载一些文件,因为一些原因下载不下来就会卡在那里,这个时候,假如用HTTP_PROXY=socks://127.0.0.1:1080,那么也还是下载不下来,为什么原因呢?因为minikube start会调用curl去下载文件,而curl不同于git,curl是http_proxy和socks是分开的。问题就出在这里。那怎么办呢?有几种解决办法:1,采用privoxy对http和socks进行互换;2,直接用nginx来作为http的代理,详见我的另一篇博文玩转nginx:用nginx搭建正向代理,支持http和https,当然假如就minikube这个来说,建议采用阿里修改的minikube,非常好用,阿里minikube

正向代理和认证

从我的那篇关于nginx代理的文章中知道,nginx或者tinyproxy从http的原理上来说,因为是短连接,所以天生对于认证不方便或者实现不了(注意,这里的认证不是说nginx带的安全认证,那个安全认证在代理中没有用,你总不可能让你所访问的网站上带上你的认证信息吧)。

所以,对于socks来说,在用户和代理之间再加一个代理客户端,这个代理客户端就是和代理进行长连接,当然也具有认证的功能。

反向代理和正向代理


反向代理:关键点是用户在外网,代理在内网。

正向代理:关键点是用户和代理都在内网中。

玩转nginx:用nginx搭建正向代理,支持http和https

什么是代理,什么是正向代理,什么是反向代理,详见代理那些事

用nginx搭建反向代理,这个是nginx最大的功能,网上资料一大堆,这里不再复述。

用nginx搭建正向代理,假如只是http的代理,那么网上也是一大堆,也都是正确的;但是https的代理,网上所讲得一大堆,绝大部分是错误的。(吐槽一下,明明是很明显错误的内容,难道作者都没有验证一下就发出来吗?)希望读者能通过搜索引擎找到我这一篇文章,而不是先找到那些错误的文章,耗时耗精力。

nginx的正向代理支持https,其实也是很简单,只要 nginx 加入 ngx_http_proxy_connect_module 这个模块即可。(所以网上的文章假如说支持https的nginx正向代理没有提到 ngx_http_proxy_connect_module 这个模块,看都不用看,是错误的)

下面是安装步骤:
#所有操作在 /opt 目录下
cd /opt
git clone https://github.com/chobits/ngx_http_proxy_connect_module.git
wget http://nginx.org/download/nginx-1.14.2.tar.gz
tar xzvf nginx-1.14.2.tar.gz
cd nginx-1.14.2
patch -p1 < /opt/ngx_http_proxy_connect_module/patch/proxy_connect_xxxx.patch
#注意这里选用的补丁文件proxy_connect_xxxx.patch需要参照 https://github.com/chobits/ngx_http_proxy_connect_module 中的nginx不同版本来决定,如下所示:

因为我们的nginx是1.14版本,参照上面,所以上面的命令应该是:
patch -p1 < /opt/ngx_http_proxy_connect_module/patch/proxy_connect_1014.patch
假如不成功,会显示错误信息。成功后,进行编译nginx
./configure --add-module=/path/to/ngx_http_proxy_connect_module
make && make install

然后nginx的配置如下:

server {
        listen 6281;
        resolver 114.114.114.114;

        proxy_connect;
        proxy_connect_allow            443 563;
        proxy_connect_connect_timeout  10s;
        proxy_connect_read_timeout     10s;
        proxy_connect_send_timeout     10s;

        location / {
                #下面的居然是 http 而不是 https,很奇怪的设置,但是事后验证确实没有问题
                proxy_pass      http://$host;
                proxy_set_header        Host $host;
        }
}

然后启动nginx,这样即可支持 https 的正向代理。

另外多说两句:
1. 一定要知道这样一件很重要的事情,nginx的 http/https的代理,是不支持认证的(不是说nginx不支持认证,而是这种机制下认证是没有用的),所以该代理只能放在内网里,否则你就等着哪一天突然你的外网nginx被别人使用了。
2. 支持 http/https 的代理,还可以用 tinyproxy。