技術方案選型是件頗有意思的事,各個環節都有各類選擇,能夠組合出各類可能。在這些可能性中,挑選出最佳方案,是我很喜歡作的事。css
最近剛剛完成 Klib 的標註分享,趁着熱乎勁,小結一下:過程當中糾結了哪些方案,之後最後選擇了什麼。html
這就是 Klib 分享標註的操做流程:點擊分享,當即獲得能夠全球訪問的網頁。操做不能更簡單,背後的技術邏輯卻很複雜:python
實際的開發是混在一塊兒的、思路也是交叉的,不過,爲了介紹方便,我大體按照數據流來推演。mysql
這部分的功能比較直接:Klib 將標註內容發送給接口服務器,服務器處理完後返回結果。nginx
須要介紹的,卻是功能以外的東西:sql
這部份內容實際上是很複雜的,我最終採用了和 Klib 安全性相稱的方案。數據庫
這是最基礎、但很是有效的方式,全程使用 https 加密,已經能夠大大提升安全性。編程
若是接口是公開的、全部人均可以任意訪問,就能夠隨意地向服務器丟垃圾數據,迅速將服務器擠爆。flask
好比好的作法是 使用非對稱加密,即便用一對私鑰、公鑰,使用 私鑰加密 的數據,只能使用 公鑰解密;反之,使用 公鑰加密 的數據,只能使用 私鑰解密。總體流程大體以下:api
公鑰 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 客戶端向 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 帶寬的小水管,併發時速度會很慢。因而,這一方案也被放棄。
…
如何實現 全國秒開、及更多詳盡分析,盡在「自在開發」公衆號: