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

原文連接:xcoder.in/2016/07/22/…javascript

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

在以前《跟我一塊兒部署和定製 CNPM——基礎部署》中提到過,CNPM 配置項裏面有一項配置 nfs,它所對應的是一個 NFS 對象。node

在同步 package 的時候,CNPM 會把源站的包下載到本地,而後傳給 NFS 對象相應的函數交予去處理,由 NFS 對象返回處理結束以後該包在咱們本身部署的 CNPM 對應的包下載連接。git

上面的這一套流程就給咱們自定義包存儲提供了可能,好比咱們能夠把包同步到又拍雲存儲、阿里雲 OSS 等地方去,也能夠以二進制的形式存入咱們本身的數據庫(不推薦),甚至能夠什麼都不用作直接放在本地,而後把本地文件對外網暴露便可。github

NFS 接口

NFS 的接口是實現定義好的,咱們若是要寫一個本身的 NFS 類,只須要按照約定的接口實現他們的邏輯便可。數據庫

雖然我本身不喜歡,可是 NFS 的全部函數須要在菊花函數中被實現。npm

下面給出接口的定義:xcode

  • 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 是 github.com/cnpm/oss-cn…app

打開 index.js 咱們能看到,的確 OssWrapper 實現了上面的一些接口。函數

構造函數

function OssWrapper 裏面咱們看到它 newali-oss 對象。

if (options.cluster) {
  options.schedule = options.schedule || 'masterSlave';
  this.client = new oss.ClusterClient(options);
} else {
  this.client = oss(options);
}複製代碼

也就是說在各類上傳等函數裏面都是以這個 client 爲主體作的事情的。

upload 和 uploadBuffer

首先咱們看看 upload 函數,從外部傳進來文件的 key,NFS 對象將該文件以 key 爲名傳到 OSS 去,並返回該文件上傳以後在 OSS 上的地址。

proto.upload = function* (filePath, options) {
  const key = trimKey(options.key);
  // https://github.com/ali-sdk/ali-oss#putname-file-options
  const result = yield this.client.put(key, filePath, {
    headers: this._defaultHeaders,
  });
  if (this._mode === 'public') {
    return { url: result.url };
  }
  return { key: key };
};複製代碼

uploadBuffer 其實也同樣,參數第一個 fileBuffer 是一個文件二進制 Buffer 對象,而 ali-oss 包的 put 函數第二個參數既能夠傳一個文件路徑,也能夠傳一個 Buffer,因此至關於把 upload 這個函數直接拿過來就能用了,因而就有了:

proto.uploadBuffer = proto.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 函數:

function trimKey(key) {
  return '_snpm_/' + (key ? key.replace(/^\//, '') : '');
}複製代碼

這下全部在咱們內部 CNPM 裏面的包的連接都多了個 _snpm_/ 的前綴了。

CNPM 調用解析

上面解析了接口以後,咱們來扒一扒何時會調用上面實現的接口們吧,這樣就知道 CNPM 對於 NFS 使用的工做原理了。

controllers/registry/package/download.js

源碼參考

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

/{package}/download/{package}-{version}.tgz複製代碼

而後在裏面判斷一下若是 NFS 對象有實現 url() 函數的話,先用 url() 函數生成對該包而言的真實下載連接。

讀出這個包的 registry 信息,裏面若是沒有 dist 等參數的話直接 302 到剛生成的地址去。

if (typeof nfs.url === 'function') {
  if (is.generatorFunction(nfs.url)) {
    url = yield nfs.url(common.getCDNKey(name, filename));
  } else {
    url = nfs.url(common.getCDNKey(name, filename));
  }
}

if (!row || !row.package || !row.package.dist) {
  if (!url) {
    return yield* next;
  }
  this.status = 302;
  this.set('Location', url);
  _downloads[name] = (_downloads[name] || 0) + 1;
  return;
}複製代碼

接下去是涉及到上一章沒有提到過的一個配置參數,叫 downloadRedirectToNFS,默認爲 false。若是該值爲 true 的話而且剛纔由 url() 函數生成了下載連接的話,也是直接 302 到真實下載連接去。

if (config.downloadRedirectToNFS && url) {
  this.status = 302;
  this.set('Location', url);
  return;
}複製代碼

不過若是自己 registry 裏面就沒 key 這個選項的話也會直接用 url() 生成的連接給跳過去。若是沒有 url() 的連接,那麼直接用 registry 裏面的 tarball 字段。

var dist = row.package.dist;
if (!dist.key) {
  url = url || dist.tarball;
  this.status = 302;
  this.set('Location', url);
  return;
}複製代碼

上面若是都跳過去了,那麼說明要開始調用事先寫好的 download 那兩個函數了,把文件讀到 Buffer 裏面,而後把 Buffer 放到 Response 裏面傳回去。

controllers/registry/package/remove.js

源碼參考

對於刪除包來講,除了把包從數據庫刪掉以外,還要循環遍歷一遍這個包的全部版本,把全部版本的這個包都從 NFS 裏面刪除。

try {
  yield keys.map(function (key) {
    return nfs.remove(key);
  });
} catch (err) {
  logger.error(err);
}複製代碼

這裏就調用了你事先寫好的 remove 了。固然你不實現也不要緊,最可能是包的壓縮文件不刪除而已。

controllers/registry/package/remove_version.js

源碼參考

這裏跟上一小節差很少,以前是刪除整個包,這裏是刪除包的某一個版本,因此就不用循環刪除了。

try {
  yield nfs.remove(key);
} catch (err) {
  logger.error(err);
}複製代碼

controllers/registry/package/save.js

源碼參考

而後就是用戶 $ npm publish 用的路由了,在一堆判斷以後,發佈傳過來的包被放在二進制 Buffer 內存裏面:

var tarballBuffer;
tarballBuffer = new Buffer(attachment.data, 'base64');複製代碼

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

var uploadResult = yield nfs.uploadBuffer(tarballBuffer, options);

var dist = {
  shasum: shasum,
  size: attachment.length
};

if (uploadResult.url) {
  dist.tarball = uploadResult.url;
} else if (uploadResult.key) {
  dist.key = uploadResult.key;
  dist.tarball = uploadResult.key;
}複製代碼

看到沒有,就是這裏記錄的它究竟是 key 仍是 tarball 了。

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

controller/sync_module_worker.js

源碼參考

這個文件是從源端同步相關的一些邏輯了,這裏面有兩個操做。

一個是 unpublish,調用的就是 NFS 的 remove,不做詳談了。

另外一個就是同步了。同步包會被打散成同步一個版本,而後把每一個版本同步過來。在同步版本的時候先把包文件下載到本地文件 filepath 裏面去。

var r = yield urllib.request(downurl, options);複製代碼

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

上面的代碼 options 裏面有一個文件流,連接到 filepath 目錄的這個文件去,至關於這一步就是把源端的包下載到本地 filepath 去了。

通過一堆 blahblah 的判斷(好比 SHASUM)以後,這個這個函數就會調用 NFS 的 upload 函數將本地文件名對應的文件上傳到你所須要的地方去了。

try {
  result = yield nfs.upload(filepath, options);
} catch (err) {
  logger.syncInfo('[sync_module_worker] upload %j to nfs error: %s', err);
  throw err;
}複製代碼

其結果究竟是 key 仍是 url 對於下載的影響跟前一小節一個道理。

小結

本章講了如何使用和本身定製一個 CNPM 的 NFS 層,讓包的走向跟着你的心走。在描述了開發規範和出示了樣例代碼和改造小例子以後,又解析了這個 NFS 是如何在 CNPM 裏面工做的,上面已經提到了 2.12.2 版本中全部用到 NFS 的地方。

看了上面的解析以後會對 NFS 的工做流程有更深一層的瞭解,而後就不會有寫 NFS 層的時候有種心慌慌摸不着底的狀況了。

相關文章
相關標籤/搜索