跟我一起部署和定製 CNPM——自定義包儲存層

發表於2016-07-24

CNPM 的自定義包儲存層檔案系統簡稱 NFS,我猜是 NPM File System 的意思。

在之前《跟我一起部署和定製 CNPM——基礎部署》中提到過,CNPM 配置項裡面有一項配置 nfs,它所對應的是一個 NFS 物件。

在同步 package 的時候,CNPM 會把源站的包下載到本地,然後傳給 NFS 物件相應的函式交予去處理,由 NFS 物件返回處理結束之後該包在我們自己部署的 CNPM 對應的包下載連結。

上面的這一套流程就給我們自定義包儲存提供了可能,比如我們可以把包同步到又拍雲端儲存、阿里雲 OSS 等地方去,也可以以二進位制的形式存入我們自己的資料庫(不推薦),甚至可以什麼都不用做直接放在本地,然後把本地檔案對外網暴露即可。

NFS 介面

NFS 的介面是實現定義好的,我們如果要寫一個自己的 NFS 類,只需要按照約定的介面實現他們的邏輯即可。

雖然我自己不喜歡,但是 NFS 的所有函式需要在菊花函式中被實現。

下面給出介面的定義:

  • function* upload(filepath, options)
    • filepath:檔案路徑。
    • options
      • key:待上傳檔案的標識
      • size:待上傳檔案大小
  • function* uploadBuffer(fileBuffer, options)
    • fileBuffer:待上傳檔案的 Buffer
    • options
      • key:待上傳檔案的標識
      • size:待上傳檔案的大小
  • function* remove(key)
    • key: 檔案標識
  • function* download(key, savePath, options)(可選實現)
    • key:檔案標識
    • savePath:儲存路徑
    • options
      • timeout:超時時間
  • function* createDownloadStream(key, options)(可選實現)
    • key: 檔案標識
    • options
      • timeout:超時時間
    • 返回一個 ReadStream
  • function[*] url(key)(可選實現,可以不是菊花函式)
    • key: 檔案標識

OSS-CNPM 解析

這裡拿出一個 NFS 的官方實現阿里雲 OSS 版來作為解析。它的 Repo 是https://github.com/cnpm/oss-cnpm

開啟 index.js 我們能看到,的確 OssWrapper 實現了上面的一些介面。

建構函式

function OssWrapper 裡面我們看到它 newali-oss 物件。

也就是說在各種上傳等函式裡面都是以這個 client 為主體做的事情的。

upload 和 uploadBuffer

首先我們看看 upload 函式,從外部傳進來檔案的 key,NFS 物件將該檔案以 key 為名傳到 OSS 去,並返回該檔案上傳之後在 OSS 上的地址。

uploadBuffer 其實也一樣,引數第一個 fileBuffer 是一個檔案二進位制 Buffer 物件,而 ali-oss 包的 put 函式第二個引數既可以傳一個檔案路徑,也可以傳一個 Buffer,所以相當於把 upload 這個函式直接拿過來就能用了,於是就有了:

remove、download 和 createDownloadStream

這兩個函式實際上也是直接呼叫了 ali-oss 的函式,並沒有什麼好講的,大家自己看看就好了。

url

這個函式無非就是判斷下有沒有自定義的 CDN 域名什麼的,根據不同的返回不同的網址而已。

trimKey

key 裡面帶的最前面的斜槓去掉。

我的 OSS-CNPM 隨意改造

上面一節解析了 oss-cnpm 這個包的程式碼,如果官方出的幾個 NFS 包不能滿足,大家也能自己去寫一個 CNPM 儲存層的包了。

我們公司的包是直接在 OSS 上面的,所以用 oss-cnpm 並沒有什麼不妥。

不過對於阿里系本身的公司門來說,OSS 並不是什麼大事兒,對於我們來說,OSS 的 bucket 資源還是蠻稀缺的,上次就達到上限了。所以我們目前的 NPM 包跟公司別的測試業務用的是同一個 bucket。

那麼問題來了:

oss-cnpm 直接把所有檔案放在根目錄下建資料夾,太亂了,而且的確是有小可能衝突的。而這個包又不能讓人自定義字首什麼什麼的。

於是我就自己 Fork 小小改裝了一下這個包,讓它適合我們公司自己。

改裝很簡單,在上傳的目錄中加一個資料夾字首。

動的是 trimKey 函式:

這下所有在我們內部 CNPM 裡面的包的連結都多了個 _snpm_/ 的字首了。

CNPM 呼叫解析

上面解析了介面之後,我們來扒一扒什麼時候會呼叫上面實現的介面們吧,這樣就知道 CNPM 對於 NFS 使用的工作原理了。

controllers/registry/package/download.js

原始碼參考

對於包下載來說,它的路由是:

然後在裡面判斷一下如果 NFS 物件有實現 url() 函式的話,先用 url() 函式生成對該包而言的真實下載連結。

讀出這個包的 registry 資訊,裡面如果沒有 dist 等引數的話直接 302 到剛生成的地址去。

接下去是涉及到上一章沒有提到過的一個配置引數,叫 downloadRedirectToNFS,預設為 false。如果該值為 true 的話並且剛才由 url() 函式生成了下載連結的話,也是直接 302 到真實下載連結去。

不過如果本身 registry 裡面就沒 key 這個選項的話也會直接用 url() 生成的連結給跳過去。如果沒有 url() 的連結,那麼直接用 registry 裡面的 tarball 欄位。

上面如果都跳過去了,那麼說明要開始呼叫事先寫好的 download 那兩個函式了,把檔案讀到 Buffer 裡面,然後把 Buffer 放到 Response 裡面傳回去。

controllers/registry/package/remove.js

原始碼參考

對於刪除包來說,除了把包從資料庫刪掉之外,還要迴圈遍歷一遍這個包的所有版本,把所有版本的這個包都從 NFS 裡面刪除。

這裡就呼叫了你事先寫好的 remove 了。當然你不實現也沒關係,最多是包的壓縮檔案不刪除而已。

controllers/registry/package/remove_version.js

原始碼參考

這裡跟上一小節差不多,之前是刪除整個包,這裡是刪除包的某一個版本,所以就不用迴圈刪除了。

controllers/registry/package/save.js

原始碼參考

然後就是使用者 $ npm publish 用的路由了,在一堆判斷之後,釋出傳過來的包被放在二進位制 Buffer 記憶體裡面:

接下去又判斷來判斷去,最後交由 NFS 的 uploadBuffer 來上傳並得到結果。

看到沒有,就是這裡記錄的它到底是 key 還是 tarball 了。

如果你的 upload 函式返回的是 { url: 'FOO' },那麼就是 tarball 設定成該值,在下載的時候會直接 302 到 tarball 所指的地址去;如果返回的是 { key: 'key' } 的話,會在 dist 裡面存個 key,下載的時候判斷如果有 key 的話會把它傳進你的 createDownloadStream 或者 download 函式去交由你的函式生成包 Buffer 並傳回 Response。

controller/syncmoduleworker.js

原始碼參考

這個檔案是從源端同步相關的一些邏輯了,這裡面有兩個操作。

一個是 unpublish,呼叫的就是 NFS 的 remove,不作詳談了。

另一個就是同步了。同步包會被打散成同步一個版本,然後把每個版本同步過來。在同步版本的時候先把包檔案下載到本地檔案 filepath 裡面去。

urllib 是蘇千死馬他們自己寫的比較方便和適合他們自己的一個 http 請求庫。

上面的程式碼 options 裡面有一個檔案流,連結到 filepath 目錄的這個檔案去,相當於這一步就是把源端的包下載到本地 filepath 去了。

經過一堆 blahblah 的判斷(比如 SHASUM)之後,這個這個函式就會呼叫 NFS 的 upload 函式將本地檔名對應的檔案上傳到你所需要的地方去了。

其結果到底是 key 還是 url 對於下載的影響跟前一小節一個道理。

小結

本章講了如何使用和自己定製一個 CNPM 的 NFS 層,讓包的走向跟著你的心走。在描述了開發規範和出示了樣例程式碼和改造小例子之後,又解析了這個 NFS 是如何在 CNPM 裡面工作的,上面已經提到了 2.12.2 版本中所有用到 NFS 的地方。

看了上面的解析之後會對 NFS 的工作流程有更深一層的瞭解,然後就不會有寫 NFS 層的時候有種心慌慌摸不著底的情況了。

相關文章