本篇文章意在用node.js简单实现 https://news.ycombinator.com 的部分服务端功能,且nodejs的基本语法、html页面模板部分再也不作描述。尚有不足,望指正,谢谢!
<实现效果>html
<目录结构>node
一、引入模块express
//加载须要的核心模块 const http = require('http'); const url = require('url'); const path = require('path'); const fs = require('fs'); const querystring = require('querystring'); //加载须要的第三方模块,加载前需先在命令行经过npm下载,如npm i mime --save const mime = require('mime'); const template = require('art-template');
二、功能实现apache
//建立服务器 const server = http.createServer(); //该全局常量存放数据加载地址(此例中用data.json存储数据,以后会写从mongoDB中获取数据) const data_path = path.join(__dirname, './data.json'); //处理请求 server.on('request', (req, res) => { // --------[首页模块]--------- //设置路由 if (req.url == '/' || req.url == '/index') { //调用封装的从读取数据功能函数 readData(data_path, data=> { let file_path = path.join(__dirname, './views/index.html'); let obj = { list: data }; let html = template(file_path, obj); res.end(html); }); //--------如下注释部分代码为未封装读取数据功能前的代码(下文再也不重复此部分代码)--------- // 读取数据,将数据渲染到页面结构,再将结果响应给客户端 // fs.readFile(data_path, 'utf8', (err, data) => { // data = JSON.parse(data); // let file_path = path.join(__dirname, './views/index.html'); // let obj = { list: data }; // 将数据渲染到模板中并返回响应:调用封装的response对象的成员函数 res.render(file_path,obj); // }); } // -------------[详情页]-------------- else if (req.url.startsWith('/details')) { readData(data_path, data => { // 根据url中的id找到数据中的数据项,渲染到页面,将渲染结果响应给浏览器 let id = url.parse(req.url, true).query.id; let item = data.find((v, i) => { //【这里用'=='而不用'==='的缘由:v.id是number型,id是string型】 return v.id == id; }); let file_path = path.join(__dirname, './views/details.html'); res.render(file_path,item); }); } // -------------------[提交页:需针对不一样请求方式进行处理]---------------- else if (req.url == '/submit') { fs.readFile(path.join(__dirname, './views/submit.html'), (err, data) => { if (err) { return console.log('404 not found'); } res.end(data); }); } // 【若为get请求】提交后跳转到数据添加过渡页,需处理数据并重定向到首页 else if (req.url.startsWith('/add') && req.method == 'GET') { // 解析url,将查询数据转换为对象,并添加id属性 let item = url.parse(req.url, true).query; item.id = +new Date(); //确保id的惟一 // 获取json数据,将新对象追加到转为数组的数据中 readData(data_path, data => { data.unshift(item); //调用封装的将数据覆盖写入data.json的功能函数 writeData(data,() => { // 重定向并返回响应:调用封装的response对象的成员函数 res.redirect(); }); }); } // 【若为post请求】特别地,上传参数以数据流(二进制)的形式传输 else if (req.url.startsWith('/add') && req.method == 'POST') { // 监听查询数据上传,并将数据流拼接为字符串 let str = ''; req.on('data', (chunk) => { str += chunk; }); // 监听到数据上传完成,将查询字符串解析为查询对象 req.on('end', () => { let item = querystring.parse(str); // 给此条对象添加id属性,并追加到数据中,再将数据从新覆盖写入本地 item.id = +new Date(); readData(data_path, data => { data.unshift(item); writeData(data,() => { res.redirect(); }); }) }); } // ------------[静态资源:可模拟apache,开放静态资源目录下的全部资源]------------ else if (req.url.startsWith('/assets')) { let rs_path = path.join(__dirname, req.url); fs.readFile(path.join(__dirname, req.url), (err, data) => { if (err) { return console.log('404 not found'); } res.setHeader('content-type', mime.getType(rs_path)); res.end(data); }); } // -----------非路由请求页面处理----------- else { res.end('404'); } //-------[将重定向和渲染功能(都返回响应)封装为response的成员函数]---------- res.redirect=function(){ res.statusCode = 302; res.setHeader('location', '/index'); res.end(); } res.render=function(file_path,obj){ let html = template(file_path, obj); res.end(html); } });
//-------------功能函数封装-------------- // 功能:从data.json中读取数据并转换数据格式 function readData(data_path, callback) { fs.readFile(data_path, 'utf8', (err, data) => { if (err) { return console.log(err); } data = JSON.parse(data || '[]'); callback(data); }); } // 功能:转换数据格式并将数据覆盖写入data.json function writeData(data, callback) { data = JSON.stringify(data, null, 2); fs.writeFile(data_path, data, err => { if (err) { return console.log(err); } // 若写入成功则执行回调 callback(); }); }
三、开启服务器npm
server.listen(8001, () => { console.log('server is started ,pls visit http://localhost:8001'); })
在原结构基础上,将主功能页面hn.js进行自定义模块划分,即json
另外,针对每一个模块,要考虑其依赖模块、依赖参数以及依赖的其余函数。数组
<hn.js>浏览器
// 内置模块 const http = require('http'); // 引入自定义模块 const router = require('./router.js'); const extend = require('./extend.js'); const server = http.createServer(); server.on('request', (req, res) => { //调用res拓展模块功能 extend(res); //调用路由规则模块功能 router(req,res); }); server.listen(8001, () => { console.log('server is started ,pls visit http://localhost:8001'); })
<router.js>服务器
//依赖模块 const handle=require('./handle.js'); //导出功能函数 module.exports = function (req, res) { // 首页 if (req.url == '/' || req.url == '/index') { handle.index(req,res); } // 详情页 else if (req.url.startsWith('/details')) { handle.details(req,res); } // 提交页 else if (req.url == '/submit') { handle.submit(req,res); } // 若添加操做为get请求 else if (req.url.startsWith('/add') && req.method == 'GET') { handle.addGet(req,res); } // 若添加操做为post请求 else if (req.url.startsWith('/add') && req.method == 'POST') { handle.addPost(req,res); } // 静态资源 else if (req.url.startsWith('/assets')) { handle.assets(req,res); } // 非指定页面处理 else { handle.others(req,res); } }
<handle.js>app
// 一、依赖模块 const url = require('url'); const querystring = require('querystring'); const mime = require('mime'); const path = require('path'); const fs = require('fs'); // 二、依赖参数:req,res,data_path const data_path = path.join(__dirname, './data.json'); //导出功能函数 module.exports.index = function (req, res) { readData(data_path, function (data) { let file_path = path.join(__dirname, './views/index.html'); let obj = { list: data }; res.render(file_path, obj); }); } module.exports.details = function (req, res) { readData(data_path, function (data) { let id = url.parse(req.url, true).query.id; let item = data.find((v, i) => { return v.id == id; }); let file_path = path.join(__dirname, './views/details.html'); res.render(file_path, item); }); } module.exports.submit = function (req, res) { fs.readFile(path.join(__dirname, './views/submit.html'), (err, data) => { if (err) { return console.log('404 not found'); } res.end(data); }); } module.exports.addGet = function (req, res) { let item = url.parse(req.url, true).query; item.id = +new Date(); readData(data_path, function (data) { data.unshift(item); writeData(data, function () { res.redirect(); }); }); } module.exports.addPost = function (req, res) { let str = ''; req.on('data', (chunk) => { str += chunk; }); req.on('end', () => { let item = querystring.parse(str); item.id = +new Date(); readData(data_path, function (data) { data.unshift(item); writeData(data, function () { res.redirect(); }); }) }); } module.exports.assets = function (req,res) { let rs_path = path.join(__dirname, req.url); fs.readFile(path.join(__dirname, req.url), (err, data) => { if (err) return console.log('404 not found'); res.setHeader('content-type', mime.getType(rs_path)); res.end(data); }); } module.exports.others = function (req,res) { res.end('404 not found'); } // 三、依赖的其余函数 function readData(data_path, callback) { fs.readFile(data_path, 'utf8', (err, data) => { if (err) { return console.log(err); } data = JSON.parse(data || '[]'); callback(data); }); } function writeData(data, callback) { data = JSON.stringify(data, null, 2); fs.writeFile(data_path, data, (err) => { if (err) { return console.log(err); } callback(); }); }
<extend.js>
//依赖模块 const template = require('art-template'); //导出功能 module.exports = function (res) { res.redirect = function () { res.statusCode = 302; res.setHeader('location', '/index'); res.end(); } res.render = function (file_path, obj) { let html = template(file_path, obj); res.end(html); } }
<npm安装>
npm init npm i express --save npm i art-template express-art-template --save npm i body-parser --save
<main.js>
// 引入express框架 const express = require('express'); // 实例化express对象 const app = express(); // 托管静态资源 app.use('/assets',express.static('./assets')); // 配置模板引擎 app.engine('html',require('express-art-template')); // 引入并配置body-parser const bodyParser = require('body-parser'); app.use(bodyParser.urlencoded({extended:true})); // 引入外置路由模块,并挂载到app上 const router = require('./router.js'); app.use(router); //启动服务器 app.listen(8001,()=>{ console.log('server is started' ); });
<router.js>
//引入依赖模块 const express = require('express'); const path = require('path'); const fs = require('fs'); // 实例化外置路由 const router = express.Router(); // 注册路由 //-----------------首页------------------ router.get('/', (req, res) => { res.redirect('/index'); }); router.get('/index', (req, res) => { read_data(function (data) { // 渲染文件并将渲染结果响应给浏览器 res.render('index.html', { list: data }); }) }); // --------------详情页------------------- router.get('/details', (req, res) => { // 获取查询对象 let obj = req.query; read_data(function (data) { // 经过id找到全部数据中的匹配条目 let item = data.find(v => { console.log(typeof v.id, typeof obj.id); return v.id == obj.id; }); res.render('details.html', item); }); }); // -------------提交页----------------- router.all('/submit', (req, res) => { //将文件响应给浏览器 res.sendFile(path.join(__dirname, './views/submit.html')) }); // 若经过get请求添加数据 router.get('/add', (req, res) => { let item = req.query; item.id = +new Date(); read_data(function (data) { data.unshift(item); write_data(data, function () { res.redirect('/index'); }); }) }); // 若经过post请求添加数据 router.post('/add', (req, res) => { let item = req.body; item.id = +new Date(); read_data(function (data) { data.unshift(item); write_data(data, function () { res.redirect('/index'); }); }) }); // 导出router对象 module.exports = router; // ------封装读取数据和写入数据的功能函数-------- const data_path = path.join(__dirname, './data.json'); function read_data(callback) { fs.readFile(data_path, 'utf8', (err, data) => { if (err) { return console.log('404 not found'); } data = JSON.parse(data || '[]'); callback(data); }); } function write_data(data, callback) { data = JSON.stringify(data, null, 2); fs.writeFile(data_path, data, err => { if (err) { return console.log('fail to write'); } callback(); }); }