1

rfc2388表单上传文件NodeJS无依赖版本实现

 2 years ago
source link: https://thenorthmemory.github.io/post/less-dependency-of-the-rfc2388-multipart-form-data-kit-in-nodejs-env/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
rfc2388表单上传文件NodeJS无依赖版本实现 - TheNorthMemory

官方文档小分队犯了一个错误,就是试图用文本语言来表达非字符内容,整得一众开发者迷途了,社区反馈波澜滔滔。这支小分队应该每人扣一个长鹅抱宠,捐给像俺这样努力帮扶开发者的贡献者(😄)。其实rfc2388标准上有写,这个协议就是扩展HTTP文本协议,用来传输非字符内容的,引述如下:

multipart/form-data can be used for forms that are presented using representations other than HTML (spreadsheets, Portable Document Format, etc), and for transport using other means than electronic mail or HTTP. This document defines the representation of form values independently of the application for which it is used.

上述引述内容,提到3种文件,HTML文件还算是字符型文件,表格及PDF文件就已经算是二进制文件了,文件内容人类得借助专用软件翻译,才能转成可被识别内容(肉眼能直接识别的是大牛,不再此列)。受官方技术助手伙伴在某次问答亲测有效代码截图启示,特意又研读了几遍RFC协议,国庆档给抽出成无依赖ES2015版本,已内置于另一款著名支付产品SDK包中,亲测可用,以下版本是微信支付社区特供,单文件、无依赖,适合云开发集成使用。

废话说了一箩筐,还是上代码吧:

const {extname} = require('path')
/**
 * Simple and lite of `multipart/form-data` implementation, most similar to `form-data`
 *
 * ```js
 * (new Form)
 *   .append('a', 1)
 *   .append('b', '2')
 *   .append('c', Buffer.from('31'))
 *   .append('d', JSON.stringify({}), 'any.json')
 *   .append('e', require('fs').readFileSync('/path/your/file.jpg'), 'file.jpg')
 *   .getBuffer()
 * ```
 */
class Form {
  /**
   * Create a `multipart/form-data` buffer container for the file uploading.
   *
   * @constructor
   */
  constructor() {
    Object.defineProperties(this, {
      /**
       * built-in mime-type mapping
       * @type {Object<string,string>}
       */
      mimeTypes: {
        value: {
          bmp: `image/bmp`,
          gif: `image/gif`,
          png: `image/png`,
          jpg: `image/jpeg`,
          jpe: `image/jpeg`,
          jpeg: `image/jpeg`,
          mp4: `video/mp4`,
          mpeg: `video/mpeg`,
          json: `application/json`,
        },
        configurable: false,
        enumerable: false,
        writable: true,
      },

      /**
       * @type {Buffer}
       */
      dashDash: {
        value: Buffer.from(`--`),
        configurable: false,
        enumerable: false,
        writable: false,
      },

      /**
       * @type {Buffer}
       */
      boundary: {
        value: Buffer.from(`${`-`.repeat(26)}${`0`.repeat(24).replace(/0/g, () => Math.random()*10|0)}`),
        configurable: false,
        enumerable: false,
        writable: false,
      },

      /**
       * @type {Buffer}
       */
      CRLF: {
        value: Buffer.from(`\r\n`),
        configurable: false,
        enumerable: false,
        writable: false,
      },

      /**
       * The Form's data storage
       * @type {array<Buffer>}
       */
      data: {
        value: [],
        configurable: false,
        enumerable: true,
        writable: true,
      },

      /**
       * The entities' value indices whose were in `this.data`
       * @type {Object<string, number>}
       */
      indices: {
        value: {},
        configurable: false,
        enumerable: true,
        writable: true,
      },
    })
  }

  /**
   * To retrieve the `data` buffer
   *
   * @return {Buffer} - The payload buffer
   */
  getBuffer() {
    return Buffer.concat([
      this.dashDash, this.boundary, this.CRLF,
      ...this.data.slice(0, -2),
      this.boundary, this.dashDash, this.CRLF,
    ])
  }

  /**
   * To retrieve the `Content-Type` multipart/form-data header
   *
   * @return {Object<string, string>} - The `Content-Type` header With `this.boundary`
   */
  getHeaders() {
    return {
      'Content-Type': `multipart/form-data; boundary=${this.boundary}`
    }
  }

  /**
   * Append a customized mime-type(s)
   *
   * @param {Object<string,string>} things - The mime-type
   *
   * @return {Form} - The `Form` class instance self
   */
  appendMimeTypes(things) {
    Object.assign(this.mimeTypes, things)

    return this
  }

  /**
   * Append data wrapped by `boundary`
   *
   * @param  {string} field - The field
   * @param  {string|Buffer} value - The value
   * @param  {String} [filename] - Optional filename, when provided, then append the `Content-Type` after of the `Content-Disposition`
   *
   * @return {Form} - The `Form` class instance self
   */
  append(field, value, filename = '') {
    const {data, dashDash, boundary, CRLF, mimeTypes, indices} = this

    data.push(Buffer.from(`Content-Disposition: form-data; name="${field}"${filename && Buffer.isBuffer(value) ? `; filename="${filename}"` : ``}`))
    data.push(CRLF)
    if (filename || Buffer.isBuffer(value)) {
      data.push(Buffer.from(`Content-Type: ${mimeTypes[extname(filename).substring(1).toLowerCase()] || `application/octet-stream`}`))
      data.push(CRLF)
    }
    data.push(CRLF)
    indices[field] = data.push(Buffer.isBuffer(value) ? value : Buffer.from(String(value)))
    data.push(CRLF)
    data.push(dashDash)
    data.push(boundary)
    data.push(CRLF)

    return this
  }
}

module.exports = Form
module.exports.default = Form

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK