解决 Disqus 在国内不能访问的方案

2017.06.01

动机

今天,多说评论系统正式关闭了。

早在一个月前还在做毕设的时候,听闻如此噩耗,就以在纠结到底迁移到哪个评论系统比较好。

但可惜,除了多说之外,国内似乎找不到靠谱点的社会化评论系统。

畅言是搜狐出的评论系统,试用了一段时间后就弃坑了。主要缺点是不支持 facebook,twitter,github 等任何国外的平台,其次评论管理很不方便,因为后台账号不能用来评论,所以在自己网站上评论还得重新注册,好傻呀是不是。官网的用来管理评论的 chrome 插件还过期不维护了。

韩国人做的来必力,试过一下,支持的 SNS 很全,但也有问题,比如不支持评论导入。而且肉眼望过去,bug 比较多。

网易云跟帖适合灌水,对于博客网站并不是很适合。

友言就不吐槽了。

所以,转了一圈,发现还是老牌的 diqus 最靠谱,也不用担心突然哪一天它也跪了的问题。

由于众所周知的原因,disqus 在国内不能访问。但由于 disqus 提供了 API(待会再说明这玩意怎么坑),于是可以通过 API 来访问和创建评论。

于是半个多月前,就做了一些准备工作。一是确认了通过 API 访问使用 disqus 的可行性,二是大概查了一下有没有现成的轮子。于是找到了 fooleap 用 php 为 jekyll 做的一个代理。

考虑到对 php 不是很熟悉,如果要改源代码并在 hexo 里集成对于我而言有些难度,所以最终打算自己用熟悉的 Node.js 重新造个轮子,用来为 hexo 博客做 disqus 的代理。

并且,我可以自定义评论的样式,来深度整合进自己写的主题aqua,感觉会很赞。

项目地址:Disqus-Proxy
配置说明:Disqus-Proxy-Config

思路

整体流程是这样的,在前端页面上测试 disqus 加载是否成功,如果成功则显示 disqus 的评论框,反之加载独立的评论框,并将请求发送给自己在国外的 vps,利用 vps 做反向代理,接收来自客户端的请求到 disqus 服务器并再转发给客户端。

流程图大概长这样:

于是问题就来了:如何检测 disqus 是否能成功加载?

我们先来看看 disqus 的通用植入代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div id="disqus_thread"></div>
<script>
var disqus_config = function () {
this.page.url = PAGE_URL; // Replace PAGE_URL with your page's canonical URL variable
this.page.identifier = PAGE_IDENTIFIER; // Replace PAGE_IDENTIFIER with your page's unique identifier variable
};
(function() {
var d = document, s = d.createElement('script');
s.src = 'https://yourname.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
})();
</script>

可以看到 disqus 是通过创建 script 标签,通过设置 src 属性动态引入脚本的。因此,我们可以通过判断这个脚本在指定时间内加载是否完成来判断是否载入自制评论框。

这里感谢 fooleap 在 这篇文章 里提供的思路,下面是我在 React.js 实现判断的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
componentWillMount() {
const s = document.createElement('script')
const username = window.disqusProxy.username
s.src = `https://${username}.disqus.com/embed.js`
s.async = true
s.setAttribute('data-timestamp', String(+new Date()))
s.onload = () => {
this.setState({disqusLoaded: true})
}
s.onerror = () => {
this.setState({disqusLoaded: false})
console.log('Failed to load disqus. Load disqus-proxy instead.')
}
document.body.appendChild(s)
setTimeout(async () => {
if (!this.state.disqusLoaded) {
const DisqusProxy = await import('./DisqusProxy')
this.setState({DisqusProxy: DisqusProxy.default})
}
}, 3000)
}

如果在 3 秒后,脚本仍然未loaded,那么引入自制的评论框。

这里利用 ES6 中动态 import 的命令,通过 webpack 来分割代码,使得根据 disqus 加载状况,决定要不要加载剩余的模块,以此减少不必要的脚本载入。

评论框使用了 React.js,后端用了Node.jsKoa框架,负责请求转发。

关于 disqus 的 API

虽然 disqus 提供了丰富的 API,和界面友好的文档,但应用起来是有很多问题的。

首先,在服务端发通过 API 发送匿名评论是一个 非官方支持 的 API,下面是之前咨询这个问题时官方的答复邮件:

所以,通过这个 API 创建匿名评论时,并不能传递 IP 地址的参数,因为 disqus 在拿到评论时会自动以请求的 IP 地址作为评论的 IP 地址。换句话说,通过后端 API 创建评论的 IP 是固定的。就是 VPS 的地址。

为了解决这个问题,可以拿到 disqus 用户的 token 之后调用 API 评论。但这很不现实。如果通过 OAuth 2,需要跳转登录,但一是用户可能还没注册 disqus 请求匿名评论,二是既然能跳转 disqus 登录,还需要代理个毛线啊。

并且,由于 disqus 并没有提供 admin 账号的登录 API,所以无法在服务端拿到 moderator 用户的 cookie,所以不能直接审核通过匿名评论,而需要在匿名评论发布后,admin 登录到控制台审核通过评论。

由于不能指定评论的 IP,并且评论的 IP 地址只能是 VPS 主机的 IP,所以导致点赞等功能是无法通过后端 API 来搞的。

对于 https 网站

由于我的网站是位于 github 上的静态的 https 网站,所以在向我的 VPS 服务器发送 http 的 XHR 请求时就出了问题。通常来说,VPS 主机是没有证书的,所以在不购买或者不用自签名正式的情况下是不能启用 https 的。然而,在 https 网站发送请求 http 会被浏览器无情的 block 掉。

这里我的解决方案是又拍云。

又拍云提供了源站回源的功能,目的是缓存源站的静态文件。于是,我就想到了使用又拍云对前端页面 https 请求的目标地址进行 http 回源,这样相当于在前端页面与 VPS 再加了一层代理,前端页面的 https 请求通过又拍云以 http 协议请求到 VPS 服务器,得到结果后返回前端页面。这就相当于在前端页面与 disqus 服务器间架设了两层代理。

所以这样就相当于以 https 访问又拍云,再以又拍云以 http 协议访问 VPS。只要在这里不设置缓存就行了。

这会不会导致速度减慢呢?其实,国内直连国外的 VPS 主机到并不一定比连接又拍云后再由又拍云连接 VPS 速度快。毕竟又拍云的各个主机通常位于主干网络上,两边的访问都会很快,相当于一个网游加速器了。