nodejs安装下载参照菜鸟教程nodejs
可以将node.exe添加到环境变量中,直接使用node命令。
'use strict';
var http = require('http');
var fs = require('fs');
var path = require('path');
var querystring = require('querystring');
var host = process.env.host || '0.0.0.0';
var port = process.env.PORT || 1337;
http.globalAgent.maxSockets = 50;
// content-encoding-gzip 压缩 .pipe(zlib.createGzip())
var zlib = require('zlib');
var downloadcount = 1;
var uploadcount = 1;
var server = http.createServer(function (req, res) {
// 获取request的方式和后面的url
var { method, url } = req;
// 网页的请求,返回index.html
if (method === 'GET' && url === '/') {
// 内容类型是网页,把网页的流写到response中
res.writeHead(200, { 'Content-Type': 'text/html;charset=UTF-8' });
getHtml().pipe(res);
}
// 获取文件的请求,返回文件列表
else if (method === 'GET' && url === '/getfile') {
// 把文件列表用回车分隔返回,在控制台查看
var result = fileList.join('\r\n')
res.writeHead(200, { 'Content-Type': 'text/plain;charset=UTF-8' });
res.end(result);
}
// 下载文件的请求GET
else if (method === 'GET' && url != '/favicon.ico') {
if (url.startsWith('/FileServer/uploads/')) {
var fileName = url.slice('/FileServer/uploads/'.length).trim();
// 判断一下文件名是不是空
if (fileName != '') {
// 拼接文件夹开始下载文件
const filePath = path.join(__dirname, '/FileBase/', fileName);
downloadFile(filePath, res);
console.log(fileName + ' download' + "(" + downloadcount++ + ")");
}
else {
// 文件名是空的,返回文件名不能为空的响应
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('file name is null');
}
}
}
// 上传文件的请求
else if (method === 'POST' && url === '/FileServer/fileupload') {
// 获取post的数据,然后返回
getPostData(req).then((params) => {
//console.log(params);
console.log(JSON.stringify(params) + "(" + uploadcount++ + ")");
res.writeHead(200, { 'Content-Type': 'application/json;charset=UTF-8' });
res.end(JSON.stringify(params));
})
}
// 下载文件的请求
else if (method === 'POST' && url === '/download') {
// 获取post的数据
getPostData(req).then((params) => {
// 判断是不是符合标准
if (params && querystring.parse(params).type_id == 1) {
// 获取要下载的文件名
var fileName = querystring.parse(params).filename;
// 判断一下文件名是不是空
if (fileName.trim() != '') {
// 拼接文件夹开始下载文件
const filePath = path.join(__dirname, '/FileBase/', fileName);
downloadFile(filePath, res);
}
else {
// 文件名是空的,返回文件名不能为空的响应
res.writeHead(412, { 'Content-Type': 'text/html' });
res.end('file name is null');
}
}
})
}
});
// 开启服务端口
server.listen(port, host);
// 获取网页的可读流
const getHtml = () => {
return fs.createReadStream(path.join(__dirname, 'index.html'));
}
// 通用代码,buffer流的split方法需要重定义
// spl表示分隔符
Buffer.prototype.split = Buffer.prototype.split || function (spl) {
// 定义数组接收分隔出来的内容
let arr = [];
// 表示当前遍历的位置
let cur = 0;
// 用来接收spl分隔符索引到的位置
let n = 0;
// 如果索引值存在,即不为-1, 同时将索引到的位置赋值给n
while ((n = this.indexOf(spl, cur)) != -1) {
// 切割从当前位置到索引位置,再将该段字符串存到数组中
arr.push(this.slice(cur, n));
// 当前位置向后进行遍历,寻找下一个分隔符
cur = n + spl.length
}
// 退出循环再来一手,要不输出少一个
arr.push(this.slice(cur))
return arr
}
// 文件处理 --现成代码
const fileHandler = (str, buffers, req) => {
let boundary = '--' + str.split('=')[1];
// 第一步 使用boundary切割,形式类似于 ------WebKitFormBoundary8QtZlZVe7Tqym8tG
let result = buffers.split(boundary);
// 第二步 丢弃首部的空元素和尾部的--元素
result.shift();
result.pop();
// 第三步 现在只剩下两种形式
/***********************************************************************************
* *
* \r\n数据描述\r\n\r\n数据值\r\n 和 \r\n数据描述1\r\n数据描述2\r\n\r\n文件内容\r\n *
* *
***********************************************************************************/
result = result.map(buffer => buffer.slice(2, buffer.length - 2))
// 第四步 现在只剩下两种数据形式
/********************************************************************
* *
* 数据描述\r\n\r\n数据值 和 数据描述1\r\n数据描述2\r\n\r\n文件内容 *
* *
* 继续使用\r\n\r\n进行切割 *
********************************************************************/
result = result.map((buffer) => {
return buffer.split('\r\n\r\n');
})
// 第五步 组合数据
let obj = {};
result.forEach((array) => {
// 普通表单数据格式
/****************************************************
* Content-Disposition: form-data; name="type_id" *
* 1 *
****************************************************/
if (array[0].indexOf('\r\n') == -1) {
// 普通表单数据
const arr = array[0].toString().split(';');
let key = arr[arr.length - 1].split('=')[1].toString();
key = key.replace(/['"]/ig, '');
obj[key] = array[1].toString();
}
else {
// 文件数据
/***************************************************************************************
* Content-Disposition: form-data; name="photo"; filename="854138674122619089.jpg" *
* Content-Type: image/jpeg *
* *
* xxxxxxxxxxxxxx一堆二进制数据 *
***************************************************************************************/
// 承载二进制的文件数据
const fileData = array[1];
const arr = array[0].split('\r\n')[0].split(';');
let filename = arr[arr.length - 1].split('=')[1].toString();
filename = filename.replace(/['"]/ig, '');
const filePath = `/FileBase/${filename}`;
// TODO 对于文本文件存在\r\n的会进行切片,变成多个buffer,并且\r\n会不见,处理一手
var ans = 2;
if (array.length > ans) {
array.forEach(item => {
if (ans == 2) {
ans--;
}
else if (ans == 1) {
fs.writeFileSync(path.join(__dirname, filePath), item + '\r\n\r\n', (err) => {
console.error(err);
})
ans--;
}
else {
// append 追加文件内容,需要同步写入
fs.writeFileSync(path.join(__dirname, filePath), item + '\r\n\r\n', {
flag: 'a', encoding: 'utf-8'
}, (err) => {
console.error(err);
})
}
})
}
else {
// writeFile 直接打开文件默认是 w 模式,所以如果文件存在,该方法写入的内容会覆盖旧的文件内容。
fs.writeFile(path.join(__dirname, filePath), fileData, (err) => {
console.error(err);
})
}
obj.fileUrl = filePath;
obj.fileName = filename;
// 上传文件之后更新文件列表
if (!fileList.includes(filename)) {
fileList.push(filename);
}
}
})
return obj;
}
// TODO 测试
/********************************
* 300并发 *
* 网络环境不好测试并发 *
********************************/
// TODO 功能
/*
* try catch
* 并发
* 文件夹
*/
// 获取post数据
const getPostData = (req) => {
// 开启异步处理
return new Promise((resolve) => {
let chunk = [];
// 当有数据流时加到数组中
req.on('data', (buffer) => {
chunk.push(buffer);
})
// 当数据完成时开始处理
req.on('end', () => {
const buffers = Buffer.concat(chunk);
// 获取boundary分隔符,上传文件的时候在req的Content-Type中会带这个参数
let boundary = req.headers['content-type'].split('; ')[1];
// 触发文件下载
if (boundary && boundary.includes('boundary')) {
// 将文件上传解析完毕的参数赋值给boundary
boundary = fileHandler(boundary, buffers, req);
}
else {
boundary = null;
}
// 文件上传
if (boundary) {
resolve(boundary);
}
// 普通post请求,原样返回
else {
let data = buffers.toString();
resolve(data);
}
})
})
}
// 下载文件
const downloadFile = (pathUrl, res) => {
// 判断一下文件是否存在
// TODO 这里是异步调用,之后改改看
fs.exists(pathUrl, function (exists) {
// 存在文件,返回文件流
if (exists) {
try {
// 创建可读流,获取文件信息并返回
const readStream = fs.createReadStream(pathUrl);
const stats = fs.statSync(pathUrl);
const filename = path.basename(pathUrl);
// 这里中文名的文件会字符异常,需要转ASCII编码
// TODO 目前这样转码可以,之后改改看
var filenameURI = encodeURI(filename);
// 内容类型是二进制文件, 描述是附件需要下载
res.writeHead(200, {
'Content-Type': 'application/octet-stream',
'Content-Disposition': 'attachment; filename=' + filenameURI,
'Content-Length': stats.size
});
readStream.pipe(res);
}
catch (ex) {
console.error(ex);
}
}
// 不存在文件,返回文件没找到的响应
else {
console.log('文件不存在');
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('file is not found');
}
});
}
// 异步下载文件测试
const downloadFileAsync = (pathUrl, res) => {
return new Promise((resolve) => {
// 判断一下文件是否存在
// TODO 这里是异步调用,之后改改看
fs.exists(pathUrl, function (exists) {
// 存在文件,返回文件流
if (exists) {
try {
// 创建可读流,获取文件信息并返回
const readStream = fs.createReadStream(pathUrl);
const stats = fs.statSync(pathUrl);
const filename = path.basename(pathUrl);
// 这里中文名的文件会字符异常,需要转ASCII编码
// TODO 目前这样转码可以,之后改改看
var filenameURI = encodeURI(filename);
// 内容类型是二进制文件, 描述是附件需要下载
res.writeHead(200, {
'Content-Type': 'application/octet-stream',
'Content-Disposition': 'attachment; filename=' + filenameURI,
'Content-Length': stats.size
});
readStream.pipe(res);
resolve('ok');
}
catch (ex) {
console.error(ex);
}
}
// 不存在文件,返回文件没找到的响应
else {
console.log('文件不存在');
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('file is not found');
resolve('file is not found');
}
});
});
}
// 文件列表
var fileList = [];
// 获取所有文件
const getAllFile = () => {
// 读文件夹并把所有文件写到文件列表中
fs.readdir(path.join(__dirname, 'FileBase'), function (err, files) {
if (err) {
return console.error(err);
}
files.forEach(function (file) {
fileList.push(file);
})
})
}
// 初始化文件列表
getAllFile();
超过三百兆的文件最好压缩一手
注意在同级目录新建一个FileBase文件夹
源码
文件服务器git地址