Node.js HTTP服务器中的文件、图片上传的方法


Posted in Javascript onSeptember 23, 2019

HTTP协议中,multipart/form-data格式用于向服务器发送二进制数据,通过这一内容类型(Content-Type)可以实现文件、图片的上传。由于这种格式发送的是二进制数据,在服务器端接收和处理数据时会与其它内容类型有所有区别。

HTTP协议中的文件上传

最早的HTTP协议中是不支持文件上传的,在1995年制定的rfc1867规范中,在HTTP POST请求的内容类型Content-Type中扩展了multipart/form-data类型,该类型用于向服务器发送二进制数据,以便支持文件的上传。

POST上传文件

我们通过form表单提交文件时,会构造类似像下面这样一个表单:

<form enctype="multipart/form-data" action="_URL_" method="POST">
 <input name="userfile1" type="file">
 <input type="submit" value="发送文件">
</form>

在使用form提交表单数据时,默认的编码格式为application/x-www-form-urlencoded,上传文件时需要通过enctype属性将编码方式设置为multipart/form-data。

HTTP数据提交与服务器数据解析

在包含请求体的请求中,提交的数据会按指定编码类型进行编码,而客户端会按编码方式设置请求头中的Content-Type字段。
在一个application/x-www-form-urlencoded编码的请求中,会设置一个如下的请求头:

Content-Type:application/x-www-form-urlencoded

而用于文件上传的编码方式multipart/form-data,会设置一个如下的请求头:

Content-type: multipart/form-data, boundary=AaB03x

服务器数据接收与解析

对于一个编码方式为application/x-www-form-urlencoded的请求来说,会对提交内容进行URL编码。服务器会收到类似如下内容:

POST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Host: itbilu.com
Content-Length: 23
Connection: Keep-Alive
Cache-Control: max-age=0

key1=value1&key2=value2

请求头与请求体之间会有一个空行,服务器会对请求体以queryString的方式进行解码。

而对一个multipart/form-data的文件上传请求来说,收到的内容类似如下:

POST / HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryYN9YYwO9ESipYBIx
Accept-Encoding: gzip, deflate
Host: itbilu.com
Content-Length: 22646
Connection: Keep-Alive
Cache-Control: max-age=0

------WebKitFormBoundaryoqBx9oYBhx4SF1YQ
Content-Disposition: form-data; name="myName"

itbilu.com
------WebKitFormBoundaryYN9YYwO9ESipYBIx
Content-Disposition: form-data; name="upload"; filename="41GiLecHO3L.jpg"
Content-Type: image/jpeg

����JFIF��C // 文件的二进制数据
……
--------WebKitFormBoundaryYN9YYwO9ESipYBIx--

在请求头的Content-Type字段中,除了编码类型为multipart/form-data描述外,还有一个boundary属性,这是客户端随机生成的一个数据边界描述。

如上所示,文件上传时内容是分段传输的,每一boundary表示一个fild(form表单控值)边界。

如上面示例所示,上传文件时除内容描述外还包含一个的Content-Type文件MIME的描述,其后是一个空行和文件的二进制数据。所有的表单数据结束后,会有一个”?”+boundary+”?”结束符。而服务器接收到数据后,同样会根据boundary来进行数据的接收和解析。

Node.js中处理图片/文件上传

Node.js中处理文件上传的第三方模块,本站曾经介绍过使用formidable模块处理文件上传,下面简单介绍使用Node.js原生环境处理图片上传,上传文件时也可以参考处理。

首先,使用Node.js的HTTP模块创建一个HTTP服务器:

const http = require('http');
const fs = require('fs');
const util = require('util');
const querystring =require('querystring');

//用http模块创建一个http服务端
http.createServer(function(req, res) {
 if (req.url == '/upload' && req.method.toLowerCase() === 'get') {
  //显示一个用于文件上传的form
  res.writeHead(200, {'content-type': 'text/html'});
  res.end(
   '<form action="/upload" enctype="multipart/form-data" method="post">'+
    '<input type="file" name="upload" multiple="multiple" />'+
    '<input type="submit" value="Upload" />'+
   '</form>'
  );
 } else if (req.url == '/upload' && req.method.toLowerCase() === 'post') {
  if(req.headers['content-type'].indexOf('multipart/form-data')!==-1)
   parseFile(req, res)
  } else {
   res.end('其它提交方式');
  }
}).listen(3000);

在这一步中,我们创建HTTP 服务器,当GET请求时,会加载一上用于文件上传的form表单。上传文件会通过POST方式提交到服务器,这时服务端会通过parseFile函数解析并保存文件,其解析代码如下:

function parseFile (req, res) {
 req.setEncoding('binary');
 var body = '';  // 文件数据
 var fileName = ''; // 文件名
 // 边界字符串
 var boundary = req.headers['content-type'].split('; ')[1].replace('boundary=','');
 req.on('data', function(chunk){
  body += chunk;
 });

 req.on('end', function() {
  var file = querystring.parse(body, '\r\n', ':')

  // 只处理图片文件
  if (file['Content-Type'].indexOf("image") !== -1)
  {
   //获取文件名
   var fileInfo = file['Content-Disposition'].split('; ');
   for (value in fileInfo){
    if (fileInfo[value].indexOf("filename=") != -1){
     fileName = fileInfo[value].substring(10, fileInfo[value].length-1);

     if (fileName.indexOf('\\') != -1){
      fileName = fileName.substring(fileName.lastIndexOf('\\')+1);
     }
     console.log("文件名: " + fileName);
    }
   }

   // 获取图片类型(如:image/gif 或 image/png))
   var entireData = body.toString();
   var contentTypeRegex = /Content-Type: image\/.*/;

   contentType = file['Content-Type'].substring(1);

   //获取文件二进制数据开始位置,即contentType的结尾
   var upperBoundary = entireData.indexOf(contentType) + contentType.length;
   var shorterData = entireData.substring(upperBoundary);

   // 替换开始位置的空格
   var binaryDataAlmost = shorterData.replace(/^\s\s*/, '').replace(/\s\s*$/, '');

   // 去除数据末尾的额外数据,即: "--"+ boundary + "--"
   var binaryData = binaryDataAlmost.substring(0, binaryDataAlmost.indexOf('--'+boundary+'--'));

   // 保存文件
   fs.writeFile(fileName, binaryData, 'binary', function(err) {
    res.end('图片上传完成');
   });
  } else {
   res.end('只能上传图片文件');
  }
 });
}

req是一个IncomingMessage对象,而该对象又实现了ReadableStream,所以我们可以用流的方式来接收数据。数据接收完成了,按rfc1867规范进行了数据处理,并通过fs模块保存了文件。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Javascript 写的简单进度条控件
Jan 22 Javascript
比较新旧两个数组值得增加和删除的JS代码
Oct 30 Javascript
jQuery实现鼠标滑过链接控制图片的滑动展开与隐藏效果
Oct 28 Javascript
JS实现网页标题随机显示名人名言的方法
Nov 03 Javascript
jquery+css实现动感的图片切换效果
Nov 25 Javascript
再次谈论React.js实现原生js拖拽效果引起的一系列问题
Apr 03 Javascript
实例讲解使用原生JavaScript处理AJAX请求的方法
May 10 Javascript
Bootstrap栅格系统使用方法及页面调整变形的解决方法
Mar 10 Javascript
layui选项卡效果实现代码
May 19 Javascript
jQuery获取单选按钮radio选中值与去除所有radio选中状态的方法
May 20 jQuery
JavaScript对象拷贝与赋值操作实例分析
Dec 10 Javascript
WEEX环境搭建与入门详解
Oct 16 Javascript
node 文件上传接口的转发的实现
Sep 23 #Javascript
layui 上传文件_批量导入数据UI的方法
Sep 23 #Javascript
Electron 调用命令行(cmd)
Sep 23 #Javascript
layui文件上传控件带更改后数据传值的方法
Sep 23 #Javascript
原生JavaScript实现日历功能代码实例(无引用Jq)
Sep 23 #Javascript
小程序实现上下移动切换位置
Sep 23 #Javascript
微信小程序分包加载代码实现方法详解
Sep 23 #Javascript
You might like
使用sockets:从新闻组中获取文章(一)
2006/10/09 PHP
PHP遍历某个目录下的所有文件和子文件夹的实现代码
2013/06/28 PHP
PHP 获取文件权限函数介绍
2013/07/11 PHP
2个自定义的PHP in_array 函数,解决大量数据判断in_array的效率问题
2014/04/08 PHP
[原创]php逐行读取txt文件写入数组的方法
2015/07/02 PHP
php使用flock阻塞写入文件和非阻塞写入文件的实例讲解
2017/07/10 PHP
javascript prototype,executing,context,closure
2008/12/24 Javascript
jquery 应用代码 方便的排序功能
2010/02/06 Javascript
可简单避免的三个JS发布错误的详细介绍
2013/08/02 Javascript
javascript for-in有序遍历json数据并探讨各个浏览器差异
2015/11/30 Javascript
jQuery购物车插件jsorder用法(支持后台处理程序直接转换成DataTable处理)
2016/06/08 Javascript
jQuery soColorPacker 网页拾色器
2016/06/22 Javascript
简单的js计算器实现
2016/10/26 Javascript
js判断用户是输入的地址请求的路径(实例讲解)
2017/07/18 Javascript
jQuery使用bind函数实现绑定多个事件的方法
2017/10/11 jQuery
jQuery实现的页面遮罩层功能示例【测试可用】
2017/10/14 jQuery
vue.js多页面开发环境搭建过程
2019/04/24 Javascript
vue设置默认首页的操作
2020/08/12 Javascript
Vue 电商后台管理项目阶段性总结(推荐)
2020/08/22 Javascript
[03:40]2014DOTA2国际邀请赛 B神专访:躲箭真的很难
2014/07/13 DOTA
详解python的webrtc库实现语音端点检测
2017/05/31 Python
Python列表推导式与生成器表达式用法示例
2018/02/08 Python
django模板结构优化的方法
2019/02/28 Python
django搭建项目配置环境和创建表过程详解
2019/07/22 Python
解决python 执行sql语句时所传参数含有单引号的问题
2020/06/06 Python
Python实现Word文档转换Markdown的示例
2020/12/22 Python
波兰办公用品和学校用品在线商店:Dlabiura24.pl
2020/11/18 全球购物
解决方案设计综合面试题
2015/08/31 面试题
swtich是否能作用在byte上,是否能作用在long上,是否能作用在String上
2013/07/06 面试题
2014年党员公开承诺践诺书
2014/03/25 职场文书
伊索寓言教学反思
2014/05/01 职场文书
文明班集体申报材料
2014/05/23 职场文书
人力资源管理专业自荐信
2014/06/24 职场文书
广告策划的实习心得体会总结!
2019/07/22 职场文书
手把手教你制定暑期学习计划,让你度过充实的暑假
2019/08/22 职场文书
导游词之凤凰古城
2019/10/22 职场文书