Node.js HTTP服务器中处理文件与图片上传的完整指南

作者:岩茶

时间:2026-10-23

浏览:145

本文详解Node.js中如何利用原生HTTP模块处理文件与图片上传。从HTTP协议中multipart/form-data格式的原理入手,解析请求头与数据边界机制,并提供基于ReadableStream和fs模块的完整Node.js代码示例,帮助开发者理解并实现服务端文件接收与保存。

文章目录

HTTP协议中的文件上传机制

在HTTP协议体系中,multipart/form-data格式专门用于向服务器传输二进制数据。通过设置相应的Content-Type请求头,客户端可以实现文件及图片的上传功能。由于该格式传输的是二进制流,服务器端在接收和处理这类数据时,其逻辑与其他常见内容类型存在显著差异。

HTTP协议中的文件上传机制

早期的HTTP协议版本并不支持文件上传功能。直到1995年发布的rfc1867规范,才在HTTP POST请求的Content-Type字段中扩展了multipart/form-data类型。这一扩展旨在支持向服务器发送二进制数据,从而实现了文件上传的能力。

POST方式上传文件

当通过HTML 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。然而,在上传文件时,必须通过设置form标签的enctype属性,将编码方式明确指定为multipart/form-data。

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

在包含请求体的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标记对应一个form表单控件(field)的边界。

如上述示例所示,上传文件时除了内容描述外,还包含一个Content-Type字段用于描述文件的MIME类型。其后跟随一个空行以及文件的二进制数据。所有表单数据结束后,会有一个由“--”+boundary+“--”组成的结束符。服务器接收到数据后,同样依据boundary来进行数据的接收和解析工作。

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

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模块将文件保存到磁盘。

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

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章