跟我一块儿部署和定制 CNPM——自定义包存储层

原文连接:https://xcoder.in/2016/07/22/lets-cnpm-storage/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

下面给出接口的定义:segmentfault

  • function* upload(filepath, options)xcode

    • filepath:文件路径。app

    • 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 对象。

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 层的时候有种心慌慌摸不着底的状况了。

相关文章
相关标签/搜索