用Nodejs实现文件服务器(一)
来源:互联网 发布:安禄山 杨贵妃 知乎 编辑:程序博客网 时间:2024/06/05 01:03
服务器中,用户的个人数据包含用户名、密码甚至好友关系等等,这些数据直接储存在数据库中即可。但有时,用户数据还包括图片、视频甚至文件,此时应该使用一个单独的服务器用于用户文件的存储与发送。当然,对于web,文件服务器亦用于储存网页文件。
这份服务器代码仅实现了一个简单的文件服务器,接收来自API服务器的请求,储存或发送用户的照片。该服务器没有考虑当存在大量请求时的性能问题,本身对于项目来说亦功能不完善。这两个问题将在后续中一一解决。
该文件服务器使用nodejs实现。若不了解甚至没使用过nodejs可以前去http://nodejs.cn/下载,语法与javascript类似。(博主本身也仅初步接触nodejs,内容中如有不对欢迎 指正)
首先应用express模块与fs模块,这是处理url请求与文件读写必不可少的一部分。如果对这两个模块的使用不熟悉请自行查阅官方文档或一些教程。
var express = require('express');var fs = require('fs');var app = express();该文件服务器逻辑如下,对于uri为/upload的post请求返回一个上传的id;对于uri为/upload/:upload_id的put请求接收上传文件并命名存储;对于uri未/upload/:upload_id的get请求认为该id对应的上传操作已完成,返回这个id对应的上传文件的文件名,该id失效;对于uri为/download/:filename的get请求返回对应的文件。
对于id的合法性判断,可以使用id为键创建数组。此处id初始值为0,每生效一个id就讲id值加1。而对于id对应的文件名,以id为键存在另一个数组中。
var upload_ids = [];var next_id = 0;var file_names = [];app.post('/upload', function (req, res) { upload_ids[next_id] = true; res.set("upload_id", (next_id++).toString()); res.send();});
app.get('/upload/:upload_id', function (req, res) { if (upload_ids[req.params.upload_id] == true) { fs.stat(file_names[req.params.upload_id], function(err, states) { res.set('origin_size', states.size.toString()); res.set('origin_url', file_names[req.params.upload_id]); upload_ids[req.params.upload_id] = false; res.send(); }); } else res.send();});结束上传时,返回文件的大小和文件名。此处暂时将两个参数设置在response的报文中,其实应该生成一个json文件返回,将在后续处理。
很显然这是个很“粗暴”的做法。一方面,将id单纯的设为数字的累加,当APP运行较长时间,历史上传次数很多时,这个数字很可能变成一个过大的数字,也许可以考虑用当前时间加标记的方法作为id避免这种问题;另一方面,当id失效时两个数组中的对应项其实就不再有使用意义,然而因为nodejs的数组不提供删除操作这项数据依然储存在内存中,同样当历史上传次数极多时,将会造成很大的浪费。后续将提出整改方案。
接下来是文件上传部分,代码如下。
app.put('/upload/:upload_id', function (req, res) { if (upload_ids[parseInt(req.params.upload_id)] == true) { var filename = req.params.upload_id+Date.now(); file_names[req.params.upload_id] = filename; var filepath = "./upload/"+filename; req.pipe(fs.createWriteStream(filepath));
} res.send();});代码较短,逻辑方面没啥好说的,文件保存在nodejs文件的父目录的upload目录中。需要注意的是关于数据传输是使用了pipe和stream,而不是常见的
req.on("data", function(postDataChunk) { putData += postDataChunk;});数据较小时,使用这种方法当然没问题。但对于文件,一般往往以MB为单位,putData这一nodejs中的临时变量根本无法容纳如此庞大的数据量,使用这种方法只会获得残缺的文件。那或许有人问,直接把putData用append添加到文件中不行吗?答案是,不行。还是文件大小问题,对于以MB为单位的数据,是用多个http数据包发送的。即便是博主本机测试,也存在数据重发冗余、乱序、数据出错等网络问题(若缺乏计算机网络知识请自行前去了解)。除非你顺便打算熟悉熟悉计算机网络,自行实现一个与TCP协议有相同功能的程序,否则不要考虑去直接操作这些原始的http的数据,nodejs的pipe与stream可以帮助我们完成对冗余数据包的丢弃以及对数据的排序。
文件的下载,代码如下
app.get('/download/:filename', function (req, res) { var filepath = "./upload/"+req.params.filename; fs.exists(filepath, function(exist) { if (!exist) { res.send(req.params.filename+" is not found"); } else { fs.createReadStream("./upload/"+req.params.filename).pipe(res); } });});逻辑方面同样没啥好说的,就是注意使用管道流的方式发送文件。
最后,关于服务器的启动
var server = app.listen(8080, function () { console.log("listen at 8080");});这样,一份支持文件上传下载的服务器代码也算是初步完成了。
下面考虑一些应该添加的功能。上传与下载文件时,有时会中途被中断,可能是APP临时关闭,可能是网络信号不好,可能是一些其他原因。当恢复操作时,我们显然不希望一切从头再来。这里就需要添加一下对于断点的上传和下载。
对于断点上传,客户端请求上传时http报头包含offset参数,若非0则表示是继续上传文件,而offset的值由客户端先向服务器发送请求,服务器返回目前文件的大小。对于断点下载,客户端请求下载时http报头包含range参数,服务器根据range值返回对应部分内容。
添加这些功能后,完整代码如下
var express = require('express');var fs = require('fs');var app = express();var upload_ids = [];var next_id = 0;var file_names = [];app.post('/upload', function (req, res) { upload_ids[next_id] = true; res.set("upload_id", (next_id++).toString()); res.send();});app.put('/upload/:upload_id', function (req, res) { if (upload_ids[parseInt(req.params.upload_id)] == true) { var filename = req.params.upload_id+Date.now(); if (parseInt(req.headers['offset']) == 0) file_names[req.params.upload_id] = filename; else filename = file_names[req.params.upload_id]; var filepath = "./upload/"+filename; if (parseInt(req.headers['offset']) == 0) req.pipe(fs.createWriteStream(filepath)); else req.pipe(fs.createWriteStream(filepath, {'flags': 'a'})); } res.send();});app.get('/upload/:upload_id', function (req, res) { if (upload_ids[req.params.upload_id] == true) { if (req.headers['content-length'] != undefined) { fs.stat(file_names[req.params.upload_id], function(err, states) { res.set('offset', states.size.toString()); res.send(); }); } else { fs.stat(file_names[req.params.upload_id], function(err, states) { res.set('origin_size', states.size.toString()); res.set('origin_url', file_names[req.params.upload_id]); upload_ids[req.params.upload_id] = false; res.send(); }); } } else res.send();});app.get('/download/:filename', function (req, res) { var filepath = "./upload/"+req.params.filename; fs.exists(filepath, function(exist) { if (!exist) { res.send(req.params.filename+" is not found"); } else { if (req.headers['range'] == undefined) { fs.createReadStream("./upload/"+req.params.filename).pipe(res); } else { var arg = req.headers['range'].split(","); fs.createReadStream(filepath, {start: parseInt(arg[0]), end: parseInt(arg[1])}).pipe(res); } } });});var server = app.listen(8080, function () { console.log("listen at 8080");});计划是打算做一款图片相关的APP,所以文件均是图片。实际上还需要加入处理算法生成一份压缩图片,将压缩图片与原图关系保存并将两份图片一并存储。
这个坑,待我日后有空再填。。。。。。
- 用Nodejs实现文件服务器(一)
- 用NodeJS打造你的静态文件服务器
- 用NodeJS打造你的静态文件服务器
- 用NodeJS打造你的静态文件服务器
- 用NodeJS打造你的静态文件服务器
- 用NodeJS打造你的静态文件服务器
- 用NodeJS打造你的静态文件服务器
- nodeJS 静态文件服务器源码
- nodejs 创建静态文件服务器
- nodejs静态文件服务器
- nodeJs 编写文件服务器
- 用socket实现的文件服务器(1)
- 用socket实现的文件服务器(2)
- 用socket实现的文件服务器(3)
- 用socket实现的文件服务器(5)
- 用socket实现的文件服务器(4)
- 用socket实现的文件服务器(3)
- 用socket实现的文件服务器(2)
- plsql 导出数据到 excel
- 认识套接字Socket
- 文件内存映射 DLL共享 WM_COPYDATA
- 【LeetCode】 141. Linked List Cycle
- 微信支付踩坑记录
- 用Nodejs实现文件服务器(一)
- 在app的gradle中添加不审核图片类型
- PL/SQL Developer 导入或者导出CSV文件
- Java运行时常量池与final修饰符的关系
- 大小端模式
- USB固件开发
- Leetcode Permutations
- Excel中如何不让身份证号码按科学计数法显示
- 这教育,毁人不倦