09 March 2014

分三个部分:

  1. nodejs作为http client上传文件到服务器上

  2. nodejs作为http server处理文件上传的post请求

  3. nodejs作为http server处理文件下载的请求


1. 上传文件到服务器

测试环境准备,使用formidable提供文件上传处理的测试环境。

var formidable = require('formidable'),
    http = require('http'),
    util = require('util');

http.createServer(function(req, res) {
  if (req.url == '/upload' && req.method.toLowerCase() == 'post') {
    // parse a file upload
    var form = new formidable.IncomingForm();

    form.parse(req, function(err, fields, files) {
      res.writeHead(200, {'content-type': 'text/plain'});
      res.write('received upload:\n\n');
      res.end(util.inspect({fields: fields, files: files}));
    });

    return;
  }

  // show a file upload form
  res.writeHead(200, {'content-type': 'text/html'});
  res.end(
    '<form action="/upload" enctype="multipart/form-data" method="post">'+
    '<input type="text" name="title"><br>'+
    '<input type="file" name="upload" multiple="multiple"><br>'+
    '<input type="submit" value="Upload">'+
    '</form>'
  );
}).listen(8000);

下面是nodejs的发送上传文件请求的代码,假设用于上传的文件testUpload.txt位于当前目录下(uploadFilePath指定):

var fs = require("fs");
var path = require("path");
var http = require("http");

var uploadOption = {
    host: "127.0.0.1",
    port: "8000",
    path: "/upload",
    method: 'post'
};
var formFieldName = "files";
var uploadFilePath = "./testUpload.txt";//待上传的文件路径
var boundaryKey = "NodeFormBoundaryKey";//multipart的part之间的界定符

var request = http.request(uploadOption, function(res) {
    console.log('STATUS: ' + res.statusCode);
    console.log('HEADERS: ' + JSON.stringify(res.headers));
    res.setEncoding('utf8');
    res.on('data', function(chunk) {
        console.log('BODY: ' + chunk);
    });
    res.on('end', function(err) {
        console.log("UPLOAD DONE");
    });
});
request.on("error", function(e) {
    console.log('upload Error: ' + e.message);
})
request.setHeader('Content-Type', 'multipart/form-data; boundary="' + boundaryKey + '"');//设置编码方式multipart/form-data和各个part之间的界定符

var filename = path.basename(uploadFilePath);

var part = [];
part.push('--' + boundaryKey);//part开始界定符
part.push('Content-Type: application/octet-stream');
part.push('Content-Disposition: form-data; name="' + formFieldName + '"; filename="' + filename + '"');
part.push('Content-Transfer-Encoding: binary');
part.push('\r\n');
request.write(part.join('\r\n'));

var readStream = fs.createReadStream(uploadFilePath, {
    bufferSize: 4 * 1024
});
readStream.on('end', function(err) {
    if (err) {
        console.error(err);
        return;
    }
    request.end('\r\n--' + boundaryKey + '--');//请求体结束标记
    console.log("finish sending file " + uploadFilePath + ' content');
});
readStream.pipe(request, {
    end: false //readStream 读完之后,还要再补充一行请求体结束标记
}); 

关键点:multipart/form-databoundary概念,这个可以看nodejs-post文件上传原理详解,文中有form和multipart相关的HTTP协议说明。

2. 处理文件上传请求

上面已经提到的formidable,是一个专业处理post类型请求体,特别是文件上传的请求体的nodejs库。

不依赖formidable,自己手动写代码解析post请求体的方法有待补充。

3. 处理文件下载请求

这个是三个问题中处理起来最简单的:

var fs = require("fs");
var path = require("path");
var http = require("http");

http.createServer(function(req, res) {
  if (req.url == '/download') {
    var downloadFilePath = "./testDownload.js";
    var filename = path.basename(downloadFilePath);
    var filesize = fs.readFileSync(downloadFilePath).length;
    res.setHeader('Content-Disposition','attachment;filename=' + filename);//此处是关键
    res.setHeader('Content-Length',filesize);
    res.setHeader('Content-Type','application/octet-stream');
    var fileStream = fs.createReadStream(downloadFilePath,{bufferSize:1024 * 1024});
    fileStream.pipe(res,{end:true});
    return;
  }

  // show a file download link
  res.writeHead(200, {'content-type': 'text/html'});
  res.end('点击此处开始下载');
}).listen(8000);

浏览器能把http响应当做文件下载来处理,主要是依据响应头中的Content-Disposition: attachment;filename=testDownload.js来判断的。


相关链接:

  1. Formidable —— A node.js module for parsing form data, especially file uploads.
  2. nodejs-post文件上传原理详解


blog comments powered by Disqus