node 對於 formdata 數據解析處理

文件上傳數據格式:

------WebKitFormBoundaryjlaXz2OrLImcaQJb // 分界標識
Content-Disposition: form-data; name="file"; filename="hostFile.txt" // 頭字段信息
Content-Type: text/plain // 內容類型

文件內容
------WebKitFormBoundaryjlaXz2OrLImcaQJb
Content-Disposition: form-data; name="fileSize"

1024
------WebKitFormBoundaryjlaXz2OrLImcaQJb
Content-Disposition: form-data; name="fileName"

text.txt
------WebKitFormBoundaryjlaXz2OrLImcaQJb-- // 後面兩個 ‘--’ 代表結束

 

第一步:根據 request 的 data 事件監聽取得請求信息及上傳數據

第二步:取得請求頭的 content-type 字段中的 boundary 後面的分界標識值

Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryjlaXz2OrLImcaQJb
var m = this.headers['content-type'].match(/boundary=(?:"([^"]+)"|([^;]+))/i);
// m[1] || m[2]

第三步:在每次寫入數據的時候,會根據規則獲取頭字段信息與文件內容或者字段值

var self = this;
req
  .on('error', function(err) {
    self._error(err);
  })
  .on('aborted', function() {
    self.emit('aborted');
    self._error(new Error('Request aborted'));
  })
  .on('data', function(buffer) {
    self.write(buffer); // 寫入操作
  })
  .on('end', function() {
    if (self.error) {
      return;
    }
IncomingForm.prototype.write = function(buffer) {
  if (this.error) {
    return;
  }
  if (!this._parser) {
    this._error(new Error('uninitialized parser'));
    return;
  }

  this.bytesReceived += buffer.length;
  this.emit('progress', this.bytesReceived, this.bytesExpected);

  var bytesParsed = this._parser.write(buffer); // multipart_parse
  if (bytesParsed !== buffer.length) {
    this._error(new Error('parser error, '+bytesParsed+' of '+buffer.length+' bytes parsed'));
  }

  return bytesParsed;
};

// multipart_parse
MultipartParser.prototype.write = function(buffer) {
  var self = this,
      i = 0,
      len = buffer.length,
      prevIndex = this.index,
      index = this.index,
      state = this.state,
      flags = this.flags,
      lookbehind = this.lookbehind,
      boundary = this.boundary,
      boundaryChars = this.boundaryChars,
      boundaryLength = this.boundary.length,
      boundaryEnd = boundaryLength - 1,
      bufferLength = buffer.length,
      c,
      cl,
......
  for (i = 0; i < len; i++) {
......
  }
......
}

同時執行下面操作

// Content-Disposition: form-data; name="fileName"

headerField += b.toString(self.encoding, start, end);

headerValue += b.toString(self.encoding, start, end);

var m = headerValue.match(/\bname=("([^"]*)"|([^\(\)<>@,;:\\"\/\[\]\?=\{\}\s\t/]+))/i);
if (headerField == 'content-disposition') {
  if (m) {
    part.name = m[2] || m[3] || '';
  }
  // headerValue.match(/\bfilename=("(.*?)"|([^\(\)<>@,;:\\"\/\[\]\?=\{\}\s\t/]+))($|;\s)/i);
  part.filename = self._fileName(headerValue);
} else if (headerField == 'content-type') {
  part.mime = headerValue;
} else if (headerField == 'content-transfer-encoding') {
  part.transferEncoding = headerValue.toLowerCase();
}

根據 filename 字段的值來判斷是該分界內容是字段值還是文件內容

if (part.filename === undefined) {
  var value = ''
    , decoder = new StringDecoder(this.encoding);

  part.on('data', function(buffer) {
    self._fieldsSize += buffer.length;
    if (self._fieldsSize > self.maxFieldsSize) {
      self._error(new Error('maxFieldsSize exceeded, received '+self._fieldsSize+' bytes of field data'));
      return;
    }
    value += decoder.write(buffer);
  });

  part.on('end', function() {
    self.emit('field', part.name, value);
  });
  return;
}
part.on(
'data', function(buffer) { self._fileSize += buffer.length; if (self._fileSize > self.maxFileSize) { self._error(new Error('maxFileSize exceeded, received '+self._fileSize+' bytes of file data')); return; } if (buffer.length == 0) { return; } self.pause(); file.write(buffer, function() { self.resume(); }); }); part.on('end', function() { file.end(function() { self._flushing--; self.emit('file', part.name, file); self._maybeEnd(); }); });

 

根據 buffer 數據獲取頭字段信息及內容的判斷有點複雜,邏輯如此,具體操作還沒理清,未完待續......

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章