logo一言堂

微信公众号的外部链接

我之前立过旗子再也不看公众号文章了。但是昨天手痒,点进去了一篇。这篇是什么不是很重要,但一番点击之后发现无法阅读原文. 当然,这是微信的bug, 但身为技术人员,我做了一番研究来定位这个问题。让我发现了一个不小的秘密。

什么是阅读原文

做过公众号的朋友可能都清楚,在公众号文章中你是不能随便外链到腾讯以外的地方的。这篇文章讲的比较清楚。但是真的想外链怎么办呢?微信还是比较仁慈,给大家留了一条出路,就是所谓阅读原文链接。它们长这样,在微信公众号文章的最后:

阅读原文
阅读原文

这是唯一你可以做外部链接的地方,所以不少公众号都利用这个功能,链接到自己的主页上面。我想点过这个链接的人不少,就是不懂技术的人,也应该有印象,好像跳转非常之慢?昨天有段时间我根本就点不进去,故障的表象是这样的:

  • 阅读原文链接有时候工作,有时候不工作
  • 假如它工作,之后再点,包括清除cache,cookie等等都没有影响,都会持续工作很长一段时间
  • 假如它不工作,就一直不工作一段时间,你换各种浏览器都没用,返回同样的SSL故障:Error code: SSL_ERROR_RX_RECORD_TOO_LONG
  • 更换不同的网络,例如切换wifi和数据网络,有可能在两种情况切换

昨天我点不进去,就仔细分析了一下。发现事情的真相没这么简单。在工作的时候,用户点它确实能到外部,但过程是相当曲折的。

第一次请求

首先,这不是一个简单链接。它虽然长的像链接,但其实触发的是一个javascript程序,向微信大本营 mp.weixin.qq.com 发了一个相当复杂的请求,其中包含多个参数:

{"GET":{"scheme":"https","host":"mp.weixin.qq.com","filename":"/mp/advertisement_report","query":{"__biz":["MjM5MjMxMTMyOA==","MjM5MjMxMTMyOA==","MjM5MjMxMTMyOA=="],"mid":"2649187305","idx":"1","sn":"d29693fbaf28a7edda1dc167b4bc8ce6","report_type":"3","action_type":"0","url":"http://postgres.cn/docs/12/","ascene":"-1","r":"0.6202231375274944","uin":"","key":"","pass_ticket":"","wxtoken":"777","devicetype":"","clientversion":"","appmsg_token":"","x5":"0","f":"json"},"remote":{"Address":"[240d:c040:0:40::116]:443"}}}

这里还没有列cookie内部不可见的参数。我当然无法确定这是到底在干什么,但我的判断是对用户行为做记录。这第一次请求我很多次实验从不出错。

第二次请求

然后,令人费解的是,你仍然没有跳转过去。上一个请求的返回是一个没什么内容的JSON文件,但是,公众号的javascript又做了一个请求,这次请求相当简单:

{"GET":{"scheme":"https","host":"mp.weixinbridge.com","filename":"/mp/wapredirect","query":{"url":"http://postgres.cn/docs/12/"},"remote":{"Address":"203.205.239.172:443"}}}

这次请求就出现了以上对或者不对的两种情况。对的话,服务器返回一个简单的302,然后客户端就跳转了。不对的话,这个服务器 mp.weixinbridge.com 就报SSL错误。

如果你看了之上的请求内容,就发现要跳转的URL已经嵌入在这个请求之中了。所以,咋一看这个请求完全是脱裤子放屁。在客户端已经有URL了,何必再发到服务器再转一道呢?如果有不得已的理由一定要转,为什么不用第一次请求就直接转了呢?平白无故再发一次请求?本来一次请求能做的事情,要搞三次?怪不得微信里外部链接这么慢。

通过对网络数据包仔细分析,我发现了故障原因。故障发生的时候,SSL handshake确实出错了。而且是一种相当低级的错误,就是在443端口,理论上应该走HTTPS协议的地方,服务器返回未加密HTTP数据包,解密之后是乱码。应该是web服务器配置失误。

有时候对,有时候不对后面的道理也很简单。微信流量这么大,我们看到的服务器其实都不是单台,而是相当大的一个服务器集群,用HAProxy之类的东西包在一起。这个集群中有若干服务器配置错了。HAProxy的特征就是粘性,你这次用这台服务器,同一个网络地址下次很可能还是它。我少量不规范测试都能发现故障,说明配错的数量不在少数。

问题来了

mp.weixinbridge.com 是干什么的呢?网上找不到信息,但不少人都发现了这个服务器的存在,例如这里. 我上面猜测,第一个请求是做用户行为记录。第二个请求参数很简单,甚至没有cookie,只是发了一个URL到服务器端,然后服务器端把你重定向到这个URL上而已。在跳转服务器工作的时候,我尝试修改URL,没问题,我改成啥,重定向就定到啥地方。这种白痴型服务不是很无聊吗?从参数和无cookie的角度来看,它就算想干啥也干不了啥呀?

直到后来,我传了一个已知被微信屏蔽的URL过去,返回就成了这样:

已停止访问该网页
已停止访问该网页

真相大白了,这个服务器是专门屏蔽不和谐URL的。

为什么要两次访问,而且访问两个不同服务器呢?做过工程的人很好理解:用户行为记录和不和谐网站屏蔽是不同的功能模块,很有可能是不同的部门负责,有不同的管理权限。最简单的方法就是这样,两部分串行,各自管一边,谁也不管另外一边。慢就慢点呗,反正微信公众号也不是让你做任意外部跳转的,能让你有可能出来就已经很宽宏大量了。

腾讯的技术实力我们一般是佩服的,但从这两个部门所负责的工作量和这次故障情形,你不难猜出那个部门实力更强,更负责任吧。

结束语

第一,你在微信里点的任意链接腾讯都知道你是谁,何时,何地,从那里点进去的,有详细记录。你就算用浏览器打开,隐私的提升也很有限。它虽然不完全清楚你是谁了,但时间地点位置还是相当清楚的。你是谁其实联系上下文也很容易猜出来。

第二,你点的链接未必链接的出去,就算国家防火墙不屏蔽,腾讯自己还有一次屏蔽,只有两次都能过你才能过去。国家架墙我们只能认了,腾讯墙上加墙,作为用户的你真的就认了吗?

第三,一个简单故障(跳转服务器https配置)持续24小时以上没有修复,说明腾讯压根不在乎外链工作与否。这次的故障其实不算啥。我在网上找还有这样的奇葩:微信公众号阅读原文跳转链接被劫持. 至迟到2018年九月,微信外链跳转服务器完全未加密。 这次不知道怎样,但直到我发文的当下这个故障还没有修复。

用户啊,你就在微信的小世界里呆着就好,看看公众号,用用小程序,玩玩小游戏,上什么互联网啊。