本文最近一次更新于 4 年 2 个月前,其中的内容很可能已经有所发展或是发生改变。
最近和朋友聊天时,他说发现了一个云计算服务商:「ZEIT」,可以为程序免费提供托管,想问问我有没有什么好的想法。我查看了一下发现这货在全球提供的线路还真不少(亚洲大部分地区都有),用作代理软件想必体验会比较不错。
于是我首先想到搭建 ShadowSocks、V2Ray 这类代理软件,不过很可惜 ZEIT 在部署上有限制:不支持 WebSocket(当然更不支持 SOCKS5 了),且虽然线路的延迟比较低,但在带宽上却有限制,于是我暂时放弃了正向代理,把注意打到了反向代理的头上。
反向代理相比正向代理的限制不少,最大限制的在于一次只能代理一个网站。想了想还是决定代理一个搜索引擎——Startpage,用于手机等不那么方便翻墙的设备搜索。至于为什么选择它嘛,有两点原因:
- 它是一个非常「干净」的搜索引擎,隐私性做得非常好,甚至没有登录功能,用户偏好也只是用 Cookie 实现;
- 它匿名地向 Google 提交查询,再将结果返回给用户(某种意义上来说,它也算 Google 的反向代理),所以搜索质量约等于 Google。
至于为什么不选择 Google,答案也很简单,Google 会检测计算机的异常流量,一旦检测到异常,则必须通过「reCAPTCHA」检测才能继续使用。尤其是在使用反代的时候,出现检测的机率非常高,这对想迅速得到搜索引擎反馈的用户来说,无疑是一种灾难。
时隔一年,我真香了,在把本站的代理切换成 Google 一段时间之后访问时也并没有出现「reCAPTCHA」检测,因此本站会继续保持代理 Google。
⚠️:目前,我的 ZEIT 账户因为搭建的代理被太多人次访问(每个月差不多使用了 100g 的流量),已经遭到官方永久性冻结账户了。所以大家还是尽量自己搭建自己使用吧。
思路
反向代理的核心思路或者说原理其实很简单:中转服务器把来自客户端的请求发送给服务端,再将服务端的应答返还给客户端。单纯地实现这一功能也非常简单,使用 Golang,你甚至不需要借助第三方库便可搭建一个很简单的反向代理。
func Proxy(w http.ResponseWriter, r *http.Request) {
reverseURL, err := url.Parse(protocal + host)
proxy := httputil.NewSingleHostReverseProxy(reverseURL)
r.Host = host
proxy.ServeHTTP(w, r)
}
这几行代码就足以搞定 Google 的反向代理了,也能让你愉快地使用 DuckDuckGo 了,然而却无法使用 Startpage。
是的,可能因为「Startpage」本身就相当于对 Google 的反代,所以它对反向代理极其不友好,具体见下。
薛定谔的 Bug 们
绝对路径
当我运行刚刚的程序,打开浏览器并输入地址满心欢喜地看着 Startpage 的首页一点点出现时,我几乎以为已经成功了,可是当我输入关键字搜索时,打开 Firefox 的调试工具却发现它的请求资源都是从「Startpage」域名返回的。
是的,Startpage 很「聪明地」将静态文件的引用使用了绝对路径,而不是大多数网站都使用的相对路径,这意味着我还需要修改 Response Body,将原域名都替换成自己的域名,这一点倒是没什么难度(当时我是这么想的),正好 Golang 也提供了 ModifyResponse 用于 Response 的修改。
可当我代码写好了然后发现运行结果仍和原来一样时,我开始觉得有点难办了。
传输编码
造成这个问题的原因其实很明显,但我却花了半天的时间才找到:Startpage 在网页传输时启用了 GZIP 的压缩编码,因此直接替换 www.startpage.com
是行不通的,需要将 Response Body 解码之后再替换。
完成解码替换之后,终于如愿看到请求资源都是从本域名返回的,我又一次以为自己要成功了。可是当我点击搜索结果的下一页时,网页却久久处于加载之中,我开始觉得或许不应该选择代理「Startpage」了。
域名改变
打开 Firefox 的调试工具之后,发现它居然把第二页的域名给换成了与首页完全不同的二级域名——「www」会变成类似「s2-us8」、「s3-us6」这的前缀,而具体变成什么样是由首次搜索的时候随机返回的。
这个问题其实应该是无解的——除非你把所有出现的二级域名都进行代理(类似 YouTube 其实也是把每个视频的源文件放在不同域名的服务器上),不过很可惜,Startpage 只是单纯把域名换了,实测之后直接输入域名前缀也是可以正常使用的,因此只需要把代理的 URL 从 www.startpage.com
换成 s3-us6.startpage.com
就可以加载后几页的内容了。
处理 Header
在修复了以上三个 Bug 之后,搜索功能已经很完善了,不过还有一个小问题,就是用户的偏好设置无法保存,比如自定义背景、偏好语言等,点击保存按钮会 301 重定向至 www.startpage.com
页面,是的,这又是「Startpage」为反向代理设置的一道关卡。(没办法,自己选的路,哭着也要走完 : )
在修改完 301 的重定向地址后,点击保存发现虽然不会跳转到「Startpage」域名了,但是设置依然没有保存下来,一番 Debug 后发现是 Cookie 的问题,Cookie 设置的 Domain 不是同样使用的是绝对路径,「Startpage」为了不让别人反代真是煞费苦心呐!
在直接把 Cookie 的 Domian 字段干掉之后,使用起来终于和原网站无异了。
总结
虽然一开始只想着反代一个搜索引擎,但中途想着还是把普适性做得更广一些,让它能代理任意的网站,因此考虑的方面也比较多,但最终的成就感还是挺爽的,自己也对 HTTP 各字段的理解更深刻了。
目前这个反向代理工具支持文本替换、重定向替换、Cookie 替换等,源码已开源在 GitHub,部署在 ZEIT 上,如果你想部署在自己的服务器上,建议使用 master 分支。
参考: