├── .babelrc ├── .gitignore ├── 1.jpg ├── README.md ├── favicon.ico ├── fileServer.js ├── learn ├── index.html ├── output.txt ├── style.css └── test.txt ├── lib ├── database.js ├── index.js ├── requestHandlers.js ├── router.js └── server.js ├── package.json └── src ├── database.js ├── index.js ├── requestHandlers.js ├── router.js └── server.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "es2015", "stage-0", "stage-1", "stage-2", "stage-3" ], 3 | "plugins": [ 4 | "transform-strict-mode", 5 | "transform-es2015-modules-commonjs", 6 | "transform-es2015-spread", 7 | "transform-es2015-destructuring", 8 | "transform-es2015-parameters" 9 | ], 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damonare/node-sample/99cd5a31823090751d0c6469202d11f3c5604e02/1.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-sample 2 | 3 | > 本node应用实现了一个文件上传功能,涉及知识点有:服务端JavaScript、函数式编程、阻塞与非阻塞、回调、事件、内部和外部模块等等。可谓麻雀虽小,五脏俱全。 4 | 5 | - 文件上传显示在本地浏览器(允许用户上传图片,并将该图片在 6 | 浏览器中显示出来) 7 | - 平时的部分测试代码 8 | - 充分添加注释,有没说清楚欢迎联系我jztan1996@gmail.com或是直接提issue 9 | - lib文件夹内为babel转码后的ES5代码,src为ES6语法版本 10 | 11 | **操作方法** 12 | 13 | ``` 14 | //安装依赖 15 | npm install 16 | //运行应用 17 | node index 18 | ``` 19 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damonare/node-sample/99cd5a31823090751d0c6469202d11f3c5604e02/favicon.ico -------------------------------------------------------------------------------- /fileServer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import fs from 'fs'; 3 | import http from 'http'; 4 | import path from 'path'; 5 | import url from 'url'; 6 | import koa from 'koa'; 7 | 8 | const workDir = path.resolve('.'); 9 | const hostname = '127.0.0.1'; 10 | const port = 4000; 11 | 12 | console.log('Static root dir: ' + workDir); 13 | //创建文件服务器 14 | const server = http.createServer((request, response) => { 15 | const pathname = url.parse(request.url).pathname; 16 | const filePath = path.join(workDir, pathname); 17 | fs.stat(filePath, (err, stat) => { 18 | if (!err && stat.isFile()) { 19 | response.writeHead(200); 20 | fs.createReadStream(filePath).pipe(response); 21 | } else if(!!stat.isDirectory()) { 22 | response.writeHead(200); 23 | fs.createReadStream(`${filePath}/index.html`).pipe(response); 24 | } else { 25 | console.log('404 ' + request.url); 26 | response.writeHead(404); 27 | response.end('404 Not Found'); 28 | } 29 | }); 30 | }); 31 | server.listen(port, hostname, () => { 32 | console.log('The Server is running at port:4000'); 33 | }); 34 | -------------------------------------------------------------------------------- /learn/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 文件服务器 7 | 8 | 9 | 10 |
What we can do for you ?
11 | 12 | 13 | -------------------------------------------------------------------------------- /learn/output.txt: -------------------------------------------------------------------------------- 1 | 使用Stream写入二进制数据... 2 | END. -------------------------------------------------------------------------------- /learn/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | padding: 0px; 3 | margin: 0px; 4 | height: 100%; 5 | } 6 | body { 7 | font-size: 62.5%; 8 | color: #666; 9 | } 10 | div { 11 | width: 50vw; 12 | height: 50vw; 13 | margin: 0 auto; 14 | text-align: center; 15 | } 16 | -------------------------------------------------------------------------------- /learn/test.txt: -------------------------------------------------------------------------------- 1 | 使用Stream写入二进制数据... 2 | END. -------------------------------------------------------------------------------- /lib/database.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.insert = undefined; 7 | 8 | var _mongodb = require('mongodb'); 9 | 10 | var _mongodb2 = _interopRequireDefault(_mongodb); 11 | 12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 13 | 14 | //初始化连接数据库,mongodb默认端口27017 15 | var server = new _mongodb2.default.Server('localhost', 27017, { 16 | auto_reconnect: true 17 | }); 18 | //新建数据库名称learn 19 | //引入mongodb模块 20 | var db = new _mongodb2.default.Db('learn', server, { 21 | safe: true 22 | }); 23 | console.log("database is running"); 24 | 25 | var insert = exports.insert = function insert(fileName, filePath) { 26 | db.open(function (err, db) { 27 | if (!err) { 28 | /* 29 | 新建“表”pictures,这里仅为了演示node操作数据库的方式。因此只存储了文件名和文件路径。而且mongodb适合合保存JSON数据,不适合保存文件,可以考虑在JSON里面保存文件的路径名。如果实在需要把mongodb当成分布式文件系统,请用户GridFS 30 | */ 31 | db.collection('pictures', function (err, collection) { 32 | collection.insert({ 33 | filename: fileName, 34 | filepath: filePath 35 | }, function (err, docs) { 36 | console.log(docs); 37 | db.close(); 38 | }); 39 | }); 40 | } else { 41 | console.log(err); 42 | } 43 | }); 44 | }; -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _server = require('./server'); 4 | 5 | var _router = require('./router'); 6 | 7 | var _requestHandlers = require('./requestHandlers'); 8 | 9 | //创建handle对象 10 | var handle = {}; /* 11 | 本node应用实现了一个文件上传功能,涉及知识点有:服务端JavaScript、函数式编程、阻塞与非阻塞、回调、事件、内部和外部模块等等。可谓麻雀虽小,五脏俱全。 12 | */ 13 | //引入server,router,requestHandlers模块 14 | 15 | handle['/'] = _requestHandlers.start; 16 | handle['/start'] = _requestHandlers.start; 17 | handle['/upload'] = _requestHandlers.upload; 18 | handle['/show'] = _requestHandlers.show; 19 | //进行函数传递 20 | (0, _server.firstStart)(_router.route, handle); -------------------------------------------------------------------------------- /lib/requestHandlers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.show = exports.upload = exports.start = undefined; 7 | 8 | var _querystring = require('querystring'); 9 | 10 | var _querystring2 = _interopRequireDefault(_querystring); 11 | 12 | var _fs = require('fs'); 13 | 14 | var _fs2 = _interopRequireDefault(_fs); 15 | 16 | var _formidable = require('formidable'); 17 | 18 | var _formidable2 = _interopRequireDefault(_formidable); 19 | 20 | var _database = require('./database'); 21 | 22 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 23 | 24 | /* 25 | 引入Node.js 模块, child_process。之所以用它,是为了实现一个既简单又实用的非阻塞操作:exec() 26 | */ 27 | 28 | // var exec = require('child_process').exec; 29 | 30 | /* 31 | querystring从字面上的意思就是查询字符串,是node的内置模块,一般是对http请求所带的数据进行解析。querystring模块只提供4个方法,在我看来,这4个方法是相对应的。 32 | 这4个方法分别是querystring.parse和querystring.stringify,querystring.escape和querystring.unescape。 33 | querystring.stringify()序列化; 34 | querystring.parse()反序列化; 35 | querystring.escape()编码; 36 | querystring.unescape()解码; 37 | fs模块可对本地文件进行操作,也是node内置模块,具体方法可看官方文档 38 | formidable模块是Felix Geisendörfer开发的。它对解析上传的文件数据做了很好的抽象。 39 | */ 40 | 41 | var start = exports.start = function start(response, postData) { 42 | console.log('Start!!!'); 43 | //尝试下列阻塞代码,感受下阻塞和非阻塞的区别 44 | // function sleep(time){ 45 | // var startTime=new Date().getTime(); 46 | // while(new Date().getTime()' + '' + '' + '' + '
' + '' + '' + '
' + '' + ''; 65 | // 发送 HTTP 头部 66 | // HTTP 状态值: 200 : OK 67 | // 内容类型: text/plain 68 | response.writeHead(200, { 69 | "Content-Type": "text/html" 70 | }); 71 | response.write(body); 72 | response.end(); 73 | }; 74 | //引入外部模块,数据库 75 | var upload = exports.upload = function upload(response, request) { 76 | var form = new _formidable2.default.IncomingForm(); 77 | console.log("About to parse"); 78 | form.parse(request, function (error, fields, files) { 79 | /* 80 | 使用fs相关API将要上传的临时文件转变为本地文件,方便显示。 81 | */ 82 | //返回一个新建的 ReadStream 对象 83 | var readStream = _fs2.default.createReadStream(files.upload.path); 84 | //返回一个新建的 WriteStream 对象 85 | var writeStream = _fs2.default.createWriteStream("./1.jpg"); 86 | readStream.pipe(writeStream); 87 | readStream.on('end', function () { 88 | _fs2.default.unlinkSync(files.upload.path); 89 | }); 90 | //如果您没有安装mongodb数据库或是使用的其他数据库记得将下面这句注释 91 | (0, _database.insert)('1.jpg', files.upload.path); 92 | // 发送 HTTP 头部 93 | // HTTP 状态值: 200 : OK 94 | // 内容类型: text/html 95 | response.writeHead(200, { 96 | "Content-Type": "text/html" 97 | }); 98 | response.write("received image:
"); 99 | response.write(""); 100 | response.end(); 101 | }); 102 | }; 103 | 104 | var show = exports.show = function show(response, postData) { 105 | console.log("Request handler 'show' was called."); 106 | //浏览器输入http://localhost:8888/show查看效果 107 | _fs2.default.readFile("./1.jpg", "binary", function (error, file) { 108 | if (error) { 109 | response.writeHead(500, { 110 | "Content-Type": "text/plain" 111 | }); 112 | response.write(error + "\n"); 113 | response.end(); 114 | } else { 115 | response.writeHead(200, { 116 | "Content-Type": "image/jpg" 117 | }); 118 | response.write(file, "binary"); 119 | response.end(); 120 | } 121 | }); 122 | }; -------------------------------------------------------------------------------- /lib/router.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | var route = exports.route = function route(handle, pathname, response, request) { 7 | if (typeof handle[pathname] === 'function') { 8 | handle[pathname](response, request); 9 | } else { 10 | // 发送 HTTP 头部 11 | // HTTP 状态值: 404 : Not found 12 | // 内容类型: text/plain 13 | response.writeHead(404, { 14 | "Content-Type": "text/plain" 15 | }); 16 | response.write("404 Not found"); 17 | response.end(); 18 | } 19 | }; -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.firstStart = undefined; 7 | 8 | var _http = require('http'); 9 | 10 | var _http2 = _interopRequireDefault(_http); 11 | 12 | var _url = require('url'); 13 | 14 | var _url2 = _interopRequireDefault(_url); 15 | 16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 17 | 18 | //引入http模块赋值给http变量 19 | var firstStart = exports.firstStart = function firstStart(route, handle) { 20 | //调用http对象方法createServer构建服务器 21 | _http2.default.createServer(function (request, response) { 22 | var pathname = _url2.default.parse(request.url).pathname; 23 | route(handle, pathname, response, request); 24 | console.log("一次服务器请求触发"); 25 | }).listen(8888); 26 | // 终端打印如下信息 27 | console.log('Server running at http://127.0.0.1:8888/'); 28 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-sample", 3 | "version": "1.0.0", 4 | "description": "A simple node application", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "babel-node src/index.js", 8 | "build": "babel src --out-dir lib" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/damonare/node-sample.git" 13 | }, 14 | "author": "damonare", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/damonare/node-sample/issues" 18 | }, 19 | "homepage": "https://github.com/damonare/node-sample#readme", 20 | "dependencies": { 21 | "formidable": "^1.1.1", 22 | "koa": "^2.2.0", 23 | "mongodb": "^2.2.22" 24 | }, 25 | "devDependencies": { 26 | "babel-cli": "^6.24.1", 27 | "babel-core": "^6.24.1", 28 | "babel-plugin-transform-es2015-destructuring": "^6.23.0", 29 | "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", 30 | "babel-plugin-transform-es2015-parameters": "^6.24.1", 31 | "babel-plugin-transform-es2015-spread": "^6.22.0", 32 | "babel-plugin-transform-strict-mode": "^6.24.1", 33 | "babel-preset-es2015": "^6.24.1", 34 | "babel-preset-stage-0": "^6.24.1", 35 | "babel-preset-stage-1": "^6.24.1", 36 | "babel-preset-stage-2": "^6.24.1", 37 | "babel-preset-stage-3": "^6.24.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/database.js: -------------------------------------------------------------------------------- 1 | //引入mongodb模块 2 | import mongodb from 'mongodb'; 3 | //初始化连接数据库,mongodb默认端口27017 4 | const server = new mongodb.Server('localhost', 27017, { 5 | auto_reconnect: true 6 | }); 7 | //新建数据库名称learn 8 | const db = new mongodb.Db('learn', server, { 9 | safe: true 10 | }); 11 | console.log("database is running") 12 | 13 | export const insert = (fileName, filePath) => { 14 | db.open((err, db) => { 15 | if (!err) { 16 | /* 17 | 新建“表”pictures,这里仅为了演示node操作数据库的方式。因此只存储了文件名和文件路径。而且mongodb适合合保存JSON数据,不适合保存文件,可以考虑在JSON里面保存文件的路径名。如果实在需要把mongodb当成分布式文件系统,请用户GridFS 18 | */ 19 | db.collection('pictures', (err, collection) => { 20 | collection.insert({ 21 | filename: fileName, 22 | filepath: filePath 23 | }, (err, docs) => { 24 | console.log(docs); 25 | db.close(); 26 | }); 27 | }) 28 | } else { 29 | console.log(err); 30 | } 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | 本node应用实现了一个文件上传功能,涉及知识点有:服务端JavaScript、函数式编程、阻塞与非阻塞、回调、事件、内部和外部模块等等。可谓麻雀虽小,五脏俱全。 3 | */ 4 | //引入server,router,requestHandlers模块 5 | import { firstStart } from './server'; 6 | import { route } from './router'; 7 | import { start, upload, show } from './requestHandlers'; 8 | //创建handle对象 9 | const handle = {}; 10 | handle['/'] = start; 11 | handle['/start'] = start; 12 | handle['/upload'] = upload; 13 | handle['/show'] = show; 14 | //进行函数传递 15 | firstStart(route, handle); 16 | -------------------------------------------------------------------------------- /src/requestHandlers.js: -------------------------------------------------------------------------------- 1 | /* 2 | 引入Node.js 模块, child_process。之所以用它,是为了实现一个既简单又实用的非阻塞操作:exec() 3 | */ 4 | 5 | // var exec = require('child_process').exec; 6 | 7 | /* 8 | querystring从字面上的意思就是查询字符串,是node的内置模块,一般是对http请求所带的数据进行解析。querystring模块只提供4个方法,在我看来,这4个方法是相对应的。 9 | 这4个方法分别是querystring.parse和querystring.stringify,querystring.escape和querystring.unescape。 10 | querystring.stringify()序列化; 11 | querystring.parse()反序列化; 12 | querystring.escape()编码; 13 | querystring.unescape()解码; 14 | fs模块可对本地文件进行操作,也是node内置模块,具体方法可看官方文档 15 | formidable模块是Felix Geisendörfer开发的。它对解析上传的文件数据做了很好的抽象。 16 | */ 17 | 18 | import querystring from 'querystring'; 19 | import fs from 'fs' 20 | import formidable from 'formidable'; 21 | //引入外部模块,数据库 22 | import { insert } from './database'; 23 | 24 | export const start = (response, postData) => { 25 | console.log('Start!!!'); 26 | //尝试下列阻塞代码,感受下阻塞和非阻塞的区别 27 | // function sleep(time){ 28 | // var startTime=new Date().getTime(); 29 | // while(new Date().getTime()' + 49 | '' + 51 | '' + 52 | '' + 53 | '
' + 55 | '' + 56 | '' + 57 | '
' + 58 | '' + 59 | ''; 60 | // 发送 HTTP 头部 61 | // HTTP 状态值: 200 : OK 62 | // 内容类型: text/plain 63 | response.writeHead(200, { 64 | "Content-Type": "text/html" 65 | }); 66 | response.write(body); 67 | response.end(); 68 | } 69 | 70 | export const upload = (response, request) => { 71 | const form = new formidable.IncomingForm(); 72 | console.log("About to parse"); 73 | form.parse(request, (error, fields, files) => { 74 | /* 75 | 使用fs相关API将要上传的临时文件转变为本地文件,方便显示。 76 | */ 77 | //返回一个新建的 ReadStream 对象 78 | const readStream = fs.createReadStream(files.upload.path); 79 | //返回一个新建的 WriteStream 对象 80 | const writeStream = fs.createWriteStream("./1.jpg"); 81 | readStream.pipe(writeStream); 82 | readStream.on('end', function() { 83 | fs.unlinkSync(files.upload.path); 84 | }); 85 | //如果您没有安装mongodb数据库或是使用的其他数据库记得将下面这句注释 86 | insert('1.jpg',files.upload.path) 87 | // 发送 HTTP 头部 88 | // HTTP 状态值: 200 : OK 89 | // 内容类型: text/html 90 | response.writeHead(200, { 91 | "Content-Type": "text/html" 92 | }); 93 | response.write("received image:
"); 94 | response.write(""); 95 | response.end(); 96 | }); 97 | } 98 | 99 | export const show = (response, postData) => { 100 | console.log("Request handler 'show' was called."); 101 | //浏览器输入http://localhost:8888/show查看效果 102 | fs.readFile("./1.jpg", "binary", (error, file) => { 103 | if (error) { 104 | response.writeHead(500, { 105 | "Content-Type": "text/plain" 106 | }); 107 | response.write(error + "\n"); 108 | response.end(); 109 | } else { 110 | response.writeHead(200, { 111 | "Content-Type": "image/jpg" 112 | }); 113 | response.write(file, "binary"); 114 | response.end(); 115 | } 116 | }); 117 | } 118 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | export const route = (handle, pathname, response, request) => { 2 | if (typeof handle[pathname] === 'function') { 3 | handle[pathname](response, request); 4 | } else { 5 | // 发送 HTTP 头部 6 | // HTTP 状态值: 404 : Not found 7 | // 内容类型: text/plain 8 | response.writeHead(404, { 9 | "Content-Type": "text/plain" 10 | }); 11 | response.write("404 Not found"); 12 | response.end(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | //引入http模块赋值给http变量 2 | import http from 'http'; 3 | import url from 'url'; 4 | 5 | export const firstStart = (route, handle) => { 6 | //调用http对象方法createServer构建服务器 7 | http.createServer((request, response) => { 8 | const pathname = url.parse(request.url).pathname; 9 | route(handle, pathname, response, request); 10 | console.log("一次服务器请求触发"); 11 | }).listen(8888); 12 | // 终端打印如下信息 13 | console.log('Server running at http://127.0.0.1:8888/'); 14 | } 15 | --------------------------------------------------------------------------------