本篇文章意在用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(); }); }