技术方案选型是件很有意思的事,各个环节都有各种选择,可以组合出各种可能。在这些可能性中,挑选出最佳方案,是我很喜欢做的事。
最近刚刚完成 Klib 的标注分享,趁着热乎劲,小结一下:过程中纠结了哪些方案,以后最后选择了什么。
这就是 Klib 分享标注的操作流程:点击分享,立即得到可以全球访问的网页。操作不能更简单,背后的技术逻辑却很复杂:
实际的开发是混在一起的、思路也是交叉的,不过,为了介绍方便,我大致按照数据流来推演。
这部分的功能比较直接:Klib 将标注内容发送给接口服务器,服务器处理完后返回结果。
需要介绍的,倒是功能之外的东西:
这部分内容其实是很复杂的,我最终采用了和 Klib 安全性相称的方案。
这是最基础、但非常有效的方式,全程使用 https 加密,已经可以大大提高安全性。
如果接口是公开的、所有人都可以任意访问,就可以随意地向服务器丢垃圾数据,迅速将服务器挤爆。
比如好的做法是 使用非对称加密,即使用一对私钥、公钥,使用 私钥加密 的数据,只能使用 公钥解密;反之,使用 公钥加密 的数据,只能使用 私钥解密。整体流程大致如下:
公钥 A
私钥 B
和 公钥 B
公钥 A
加密 公钥 B
,并将其发送给接口服务器私钥 A
解密后,存储该客户端对应的 公钥 B
私钥 B
加密,接口服务器收到后使用 公钥 B
解密,并用 公钥 B
加密后返回数据听起来有点像绕口令?
开发上也有点麻烦,毕竟服务器还要保存每个 Klib 客户端对应的公钥。如果有多个服务器,则需要在不同服务器间同步公钥,更加麻烦。对于我这个小产品 + 实验功能来说,暂时不需要这么高的安全级别。
于是,我采用了更简单、但够用的 AES 对称加密。即 Klib 客户端和接口服务器使用相同的 AES 加密方法、同一个密码,加密请求和响应的数据;如果不能提供正确的加密,就无法使用服务器接口。
这一方案主要的风险是:黑客可以反编译 Klib 得到密码。除了 Klib 本身会编译并签名,我还在代码里加密存储密码。基本上除了跟我有八辈子解不开的愁,99.9999% 的人是不会花精力来破解这个密码的。
即使加密过的数据,最终也只是表现为一个 http 请求,而这个请求是可能被本地拦截,进而用于模拟正常用户请求。
对应的防护是,在 http 请求中加入时间戳,并对 http 头的内容部分计算 MD5 (或 CRC 等),服务器端进行验证,就可保证 http 头不被滥用。
其实,这是 OAuth 的范畴。好在,我在开发 图床神器 iPic 时,先后从客户端的角度实现了七牛、又拍、阿里云、Imgur、Flickr、Amazon S3 的 OAuth,这次实现一个简单的服务器端部分,也不算麻烦。
上面说的是在面对黑客时的防护,听着有点晕是吧?下面来说说正常情况下的身份识别。
比如:如果用户尝试停止一个分享,如何判断该用户是否有权限?
如果有账户系统,这点比较容易解决。而 Klib 尚未引用账户系统,怎么办呢?比较高级的是使用区块链(咳咳),我目前的做法是:用户使用 Klib 分享一本书的标注时,服务器会返回一个随机数。下次用户在停止分享时,只要能提供这个随机数,即判定为有效请求。在上述各种防护的前提下,可以有效地防止被恶意停止分享。
接口服务器是整个系统中最复杂的部分,它的职责比较多:
验证请求和前面的介绍是对应的,这里略过不表。
所谓接口服务器,首先就是要开放接口(开门接客)具体的,就是 http 请求的路由表。比如,当 Klib 客户端向 https://api.klib.me/share 发送数据时,要有相应的代码来接收处理这个请求。
在之前的文章 我入门 Python 后总结的基础教程 中,我已经介绍了使用 Flask 框架,这里不再重复。
同上,请参考 我入门 Python 后总结的基础教程。
另外,使用 Supervisorctl 保证服务可靠运行。
从数据存储的角度看,书的标注都是很规整的,无非是书名、作者、笔记内容等等。于是我选择了最常用的关系型数据库:MySQL
如果直接使用 SQL 语句操作数据库,既繁琐又不安全,这里我使用可称为 ORM (Object Relational Mapping) 界事实标准的 SQLAlchemy 构建 Model、操作数据库。
我本来想说「这没什么好介绍的」,但实际上,MySQL 的坑很多。比如,如果要支持 Emoji 表情,就要全程使用 utf8mb4 编码。还有很多其他的坑,此处略去一万字…
关于标注部分,Klib 发送的是 Markdown 格式,如:
# 简单思考
## 卷首语
- 商业的本质就是“持续提供用户真正想要的东西”,除此无他。
- 召集具备回应用户需求的热情与能力的员工,并为他们营造出无拘无束可最大限度地发挥其才能的环境,除此无他。
## 第一章 经商不是“打仗”
- 重要的是不断磨炼对“大众真实需求”的感知能力和使之实体化的技术。
- 音乐和体育不同,不用与任何人战斗。
需要使用 markdown 模式将其转换成 html 格式,如:
<h1>简单思考</h1>
<h2>卷首语</h2>
<ul>
<li>
<p>商业的本质就是“持续提供用户真正想要的东西”,除此无他。</p>
</li>
<li>
<p>召集具备回应用户需求的热情与能力的员工,并为他们营造出无拘无束可最大限度地发挥其才能的环境,除此无他。</p>
</li>
</ul>
<h2>第一章 经商不是“打仗”</h2>
<ul>
<li>
<p>重要的是不断磨炼对“大众真实需求”的感知能力和使之实体化的技术。</p>
</li>
<li>
<p>音乐和体育不同,不用与任何人战斗。</p>
</li>
</ul>
这里赞叹一下:Python 轮子就是多。只需轻轻地导入 markdown 模块,即可优雅地将 Markdown 转换为 html 格式,舒爽。
import markdown
html_str = markdown.markdown(markdown_str)
对于最终生成的静态网站,像 css/js 等部分都是一样的,只是页面标题、正文等内容性的东西不同。于是,使用 Jiaja 模式表示这些通用部分,并 {{ title }} 这样的标注符表示各个分享所不同的内容部分;再用 render_template 方法替换模板中的内容,即可生成对应的静态文件。
感叹:这样简洁直接的操作、无需各种复杂的配置,就能得到最后想要的东西,真真是编程中最可爱的环节。
有了静态服务器,就像是有了宝贝,不能只是自己藏着,得拿出来让大家瞧瞧,这就是静态 (Web) 服务器要干的事。
当然,静态服务器和接口服务器,在物理上可以是同一台服务器,这里只是从角色上进行区分。
在展示静态网页方面,技术选型上主要有 2 方面的需求:
其中,内容的更新对应用户分享时的 3 种操作:
好,带着「实时」创建、更新、删除 html 文件这 3 个需求,我们来看看如何提高访问速度。
首先,如何什么都不做,意味全球的用户(Klib 必须是国际性产品,得考虑全球用户,嗯)都要连接这台服务器。
且不说并发数等限制,单从网速上看,如果将服务器放在国内,国外用户势必慢;反之亦然。更何况国内还是电信、网通、以及神奇的长宽,国外也有 N 多国家。
如果确实要这么做,比较好的方案是使用 阿里云香港服务器,可以兼顾国内国外用户。暂时,不采用这一方案,每月省下 $19 …
进而,通常的做法是使用 CDN.
CDN 确实可以有效提高不同地区、不同网络环境下的访问速度,且极大地降低对静态服务器的压力。不过,CDN 有个致命的局限:内容更新慢。尤其在更新、删除内容时,这种慢会带来业务上的问题。
比如,用户在 Klib 中分享标注后又停止,却发现之前产生的网页依然可以访问,用户会觉得这是 Bug,进而会带来很大的客服压力。于是,跳过这一方案。
下一方案是:国内、国外各一台(或多台)服务器,通过 DNS 服务器进行分流,相当于自建 CDN。
不料,却遇到一个坑:国内服务器的外网速度普遍较慢。比如我试了阿里云上海节点,从国外服务器使用 scp 或 rsync 传输一个 10 KB 的文件需要 4s,跌破了我的眼镜。并且,阿里云我也只买了 1 MB 带宽的小水管,并发时速度会很慢。于是,这一方案也被放弃。
…
如何实现 全国秒开、及更多详尽分析,尽在「自在开发」公众号:
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.