本篇文章意在用node.js简单实现https://news.ycombinator.com 的部分服务端功能,且nodejs的基本语法、html页面模板部分不再做描述。尚有不足,望指正,谢谢!
<实现效果>
一、原生js实现
<目录结构>
1、引入模块
//加载需要的核心模块
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');
2、功能实现
//创建服务器
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();
});
}
3、开启服务器
server.listen(8001, () => {
console.log('server is started ,pls visit http://localhost:8001');
})
二、模块化
在原结构基础上,将主功能页面hn.js进行自定义模块划分,即
- hn.js的功能:创建服务器、整体结构;
- router.js的功能:路由规则结构;
- handle.js的功能:每个路由规则的具体处理;
- extend.js的功能:封装为request对象的成员函数。
另外,针对每个模块,要考虑其依赖模块、依赖参数以及依赖的其他函数。
<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>
// 1、依赖模块
const url = require('url');
const querystring = require('querystring');
const mime = require('mime');
const path = require('path');
const fs = require('fs');
// 2、依赖参数: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');
}
// 3、依赖的其他函数
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);
}
}
三、express实现
<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();
});
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。