本文首發于我的部落格 阿裡雲函數計算 + Aglio 實作 API Blueprint markdown 渲染
這次想聊聊我在寫的站裡的一個核心的功能——API 文檔的編輯與展示。我最初的想法是,使用 Markdown 來編寫文檔。但是這樣在格式上難免不好統一。跟
@BillSJC聊的時候,他向我推薦了 Swagger 和 API Blueprint。這兩個都是 API 文檔自動生成工具。對比過後,我決定選擇 API Blueprint,因為它是使用類似 markdown 的文法進行文檔編寫的,相比 Swagger 的使用 YAML,API Blueprint 寫的 markdown 可讀性更強一些:
# GET /message
+ Response 200 (text/plain)
Hello World!
地鼠不友好
API Blueprint 的官網上
https://apiblueprint.org/推薦了從文檔編輯到解析、渲染的諸多工具支援。
我的需求是找到一個 API Blueprint 的渲染工具,将我在 Apicon 編寫的 markdown 渲染轉換成 HTML 文檔。但官網展示的這些工具大多都是 Node.js 編寫的,我并沒有找到支援 Golang 的工具。但我又不想額外在伺服器上再維護一個容器,用于專門運作轉換 markdown 的 Node.js 代碼。
這時,
向我介紹了 Serverless 的概念,同時推薦了阿裡雲的函數計算。
Serverless?
從字面上意思來了解 Serverless,即無伺服器。
将這個概念放在阿裡雲的函數計算服務上,即我隻需要将我實際的業務代碼上傳到函數計算服務上,然後通過約定好的觸發器去調用這個服務。在這其中,我并不需要去關心伺服器相關的硬體配置,環境配置,以及運維等這些事情。我隻需要專注于業務代碼即可。
這可能和我們平時做的 RESTful API 有些相似,RESTful API 其實也可以看做是通過一個 HTTP 觸發器,去調用相關服務。雖然二者的最終結果都是一樣的,但 RESTful API 的背後,往往是我們的伺服器一直在監聽是否有請求,一旦有請求來了,就進行處理;而函數計算這個“事件驅動”更像是一個被動的東西,被觸發後執行一段事先設定好的步驟,然後結束。
就像一個函數一樣,每次執行都是差不多的步驟和内容,前後之間的兩次調用并不會有太大的關聯和影響。我們可以來看下大廠都在用它在幹什麼:
新浪微網誌 - 調用函數将使用者上傳的圖檔進行個性化處理
115 - 觸發函數對使用者上傳的日志進行壓縮、轉換
芒果 TV - 觸發函數記錄視訊檔案的屬性資訊
石墨文檔 - 使用函數來處理多使用者文檔編輯的沖突
這麼一看我是選對了,新浪都用來處理圖檔了,那我用來處理 markdown 豈不也是合情合理?嘛,開始吧!
Aglio 渲染 markdown
API Blueprint 的渲染器我之是以選擇 Aglio
https://github.com/danielgtaylor/aglio,是因為它不僅可以自定義主題,還可以自定義模闆。這樣我就可以将頁面中我不需要的标題、導航欄等内容删除,僅保留内容主體。同時 Aglio 不僅支援 CLI 使用,還可以當做一個包,放在項目中調用。
我們先在本地實作一個 markdown 的渲染,再将它改寫成函數計算的模式。
先把 Aglio 給拉下來:
yarn add aglio
然後我們就可以開始編寫主要的代碼了:
let aglio = require('aglio')
let blueprint = `
# GET /message
+ Response 200 (text/plain)
Hello World!
`
options = {
themeTemplate: 'index.jade'
};
aglio.render(blueprint, options, function (err, html, warnings) {
if (err) return console.log(err);
if (warnings) console.log(warnings);
console.log(html);
});
這裡我使用的是 Aglio 的預設主題,你可以在 Aglio GitHub Repo 的
olio-theme
分支下載下傳到主題需要的
index.jade
與
mixins.jade
這兩個模闆檔案。然後就可以開始魔改這兩個
jade
檔案了:
doctype
include mixins
html
head
meta(charset="utf-8")
style!= self.css
body.preload
div
.row
.content
block content
+Content('primary', false)
我這邊将導航欄,标題欄、不用的 JavaScript 都去掉了。然後
mixins.jade
也是按需修改,相關英文的提示我也進行了漢化。最後的效果是這樣的:
下面我們來将其部署到阿裡雲函數計算上。
部署到阿裡雲函數計算
因為我們這裡的檔案很多,故使用阿裡雲提供的
fun
工具來進行部署。
Mac 安裝
fun
:
brew install fun
安裝好後,先輸入
fun config
來跟着指令行提示進行基本的設定:
Aliyun Account ID (阿裡雲ID)
Aliyun Access Key ID *************** (AK)
Aliyun Secret Access Key *************** (SAK)
Default region name cn-hongkong (地區,我是選擇跟自己輕量應用一樣的地區,友善直接内網調用)
The timeout in seconds for each SDK client invoking 300 (上傳代碼逾時時間)
The maximum number of retries for each SDK client 3 (上傳失敗後的嘗試次數)
Allow to anonymously report usage statistics to improve the tool over time? No (是否幫助阿裡雲改進此工具)
有坑注意
第一項
Aliyun Account ID
填寫的是阿裡雲的賬号 ID,并不是登入時填寫的賬号。賬号 ID 可以在
基本資料
-
安全設定
中找到。
之後就可以執行
fun init
來初始化一個服務了。它會給我們建立
template.yml
檔案,我們進行如下配置:
ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
Aglio-Function:
Type: 'Aliyun::Serverless::Service'
Properties:
Description: 'Markdown render'
Aglio-Function:
Type: 'Aliyun::Serverless::Function'
Properties:
Handler: index.handler
Runtime: nodejs8
CodeUri: './'
Events:
http-test:
Type: HTTP
Properties:
AuthType: ANONYMOUS
Methods: ['POST']
注意這裡的
Events
,即建立了一個 HTTP 觸發器,僅允許使用 POST 進行調用,且不做鑒權。
在所有觸發器中,HTTP 觸發器隻能在建立服務時添加,之後無法再添加。是以,在使用
fun
部署項目時,我們通過
template.yml
檔案建立。
下面編寫
index.js
,将我們的業務代碼與阿裡雲函數計算的入口進行對接:
var getRawBody = require('raw-body');
module.exports.handler = function(req, resp, context) {
let options = {
themeTemplate: 'index.jade'
};
let aglio = require('aglio')
getRawBody(req, function(err, body) {
aglio.render(body.toString(), options, function (err, html, warnings) {
if (err) return console.log(err);
if (warnings) console.log(warnings);
resp.send(html);
});
});
這裡通過引入
require('raw-body');
來擷取請求的 body,通過 Aglio 處理後傳回。
之後執行
fun deploy
部署服務。注意:
node_modules
檔案夾的内容也要一并上傳,函數計算不會再次進行拉包。
魔改 Aglio
在阿裡雲函數計算的控制台中調試我們剛才部署的函數,發現會報 502 錯誤。
問題出在 Aglio 在渲染 markdown 時,會往
node_modules
目錄下寫入一個
JavaScript
的臨時緩存檔案。但阿裡雲的函數計算隻開放了
/tmp
目錄下的寫入權限,其餘都是隻讀。
那麼這裡我隻能魔改 Aglio 源碼了。還好函數計算不會使用包管理拉包,它就隻用我們傳上去的
node_modules
。
問題出在 Aglio 的檔案寫入那裡,我們隻需更改寫檔案的目錄即可:
/node_modules/aglio-theme-olio/lib/main.js
檔案,第 144 行與第 249 行:
// Line 144
//compiledPath = path.join(ROOT, 'cache', (sha1(key)) + ".css");
compiledPath = path.join('/tmp', (sha1(key)) + ".css");
// Line 249
//compiledPath = path.join(ROOT, 'cache', (sha1(key)) + ".js");
compiledPath = path.join('/tmp', (sha1(key)) + ".js");
然後寫入的 JavaScript 的臨時檔案中,有使用
require
引入
node_modules
目錄其它的包,這裡的相對路徑我們改成絕對路徑,否則還是會報錯 502。
還是在
main.js
檔案下,第 236 行:
// Line 236
//return compiled = "var jade = require('jade/runtime');\n" + (jade.compileFileClient(filename, options)) + "\nmodule.exports = compiledFunc;";
return compiled = "var jade = require('/code/node_modules/jade/runtime');\n" + (jade.compileFileClient(filename, options)) + "\nmodule.exports = compiledFunc;";
之後再部署,就可以看到啦~
總結一下吧~
又學到了新的玩意兒呢。對于一些功能單調、而又十分常用的功能,又不想專門放在伺服器上花心思去維護,不妨可以嘗試一下阿裡雲的函數計算。Serverless 這個東西,用得好真的可以解決很多問題。
阿裡雲的
fun
工具很好用,讓我能快速部署進而能進行調試。這一次下來也踩了很多坑,感覺阿裡雲的這個僅
/tmp
目錄可寫的設定就很蠢。他為何不能改成目前代碼執行目錄可寫呢?然後 Aglio 方面,Aglio 可以說是 API Blueprint 目前可定制度最高的工具了,這點不可否認。像臨時緩存檔案目錄寫死在
node_modules
,這個從開發的角度其實也能了解啦。我一開始也不相信自己能魔改成功的哈哈。
然後關于函數計算的定價方面:
這個真的不用太擔心,阿裡雲财大氣粗。
每月免費次數 100 萬次!!
128Mb 每月免費秒數:3200000秒!!換算一下大概是 37 天。也就是說我即使一個月内一秒一次地調用都夠。
是以對于個人使用者來說,函數計算真的是完全免費了。
這裡需要注意一下阿裡雲記憶體的計算方式,他并不是按照函數運作時的實際記憶體占用計算的,而是按照你給函數運作環境設定的執行記憶體計算。
也就是說我設定 1024 Mb 的最大執行記憶體,即使實際調用時遠遠沒有用到這麼多,阿裡雲還是按照 1024 Mb 來計費。
推薦是設定實際運作記憶體占用的兩倍大小。像我這個 markdown 渲染,一次實際占用記憶體 66 Mb 左右,是以我選擇的是 128 Mb 的執行記憶體。
接下來要做的就是關掉函數計算的公網通路,然後讓 ECS 從内網調用它。如果可以的話,再把函數計算的日志給打開,後面還要繼續探索。(≧∇≦)ノ