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 数据获取头字段信息及内容的判断有点复杂,逻辑如此,具体操作还没理清,未完待续......
每一次的记录,都是向前迈进的一步