您好,登錄后才能下訂單哦!
這篇文章主要介紹了NodeJS基礎(chǔ)API搭建服務(wù)器的示例分析,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
項目介紹
有一個簡單的食品店網(wǎng)站,它包括一個主頁index和一個詳情頁detail。主頁展示食品店的所有食品,包括食品圖片、名稱、價格3個信息,如下圖所示。
用戶點擊任何一項食品就會跳轉(zhuǎn)到對應(yīng)的詳情頁,包括食品圖片、名稱、價格和描述4個信息,如下圖所示。
項目結(jié)構(gòu)
項目的文件結(jié)構(gòu)如下所示。
node-example |--data(存放項目數(shù)據(jù)的文件夾) |--detail.json(存放食品詳情數(shù)據(jù)) |--foods.json(存放首頁食品數(shù)據(jù)) |--model(提供訪問和操作數(shù)據(jù)服務(wù)的數(shù)據(jù)模型) |--detail.js(詳情數(shù)據(jù)訪問模塊) |--foods.js(食品數(shù)據(jù)訪問模塊) |--public(存放css,js,圖片等靜態(tài)文件) |--css(存放css文件的文件夾) |--img(存放圖片的文件夾) |--js(存放js文件的文件夾) |--route(路由,控制器) |--api(處理普通請求的路由,或者叫控制器) |--static(處理靜態(tài)文件請求的路由,或者叫控制器) |--views(視圖,即用戶界面) |--index.html(主頁界面) |--detail.html(詳情頁面) |--server.js(服務(wù)器啟動文件) |--package.json(項目包信息) |--README.md(項目信息以及啟動方法描述)
本文只講解服務(wù)端編程,因此兩個簡單界面的實現(xiàn)過程這里就不再啰嗦了。假設(shè)你已經(jīng)能夠自行完成前端的界面編程,下面開始講解服務(wù)端編程。
編寫服務(wù)器
server.js中要完成服務(wù)器的創(chuàng)建和啟動,并將請求轉(zhuǎn)發(fā)給相應(yīng)的路由去處理。詳細(xì)代碼如下所示(假設(shè)我們已經(jīng)有了能夠正常工作的路由,這里采用Top-Down的思路,我們一層一層地往下寫,專注于解決每個層次的問題)。代碼中使用正則表達式來判定客戶端request是否是在請求靜態(tài)文件,如果是,則交給專門處理靜態(tài)文件請求的路由static去處理,否則交給普通請求的路由器api去處理。普通請求根據(jù)它的HTTP方法來判斷使用get或者post。最后,設(shè)置服務(wù)器監(jiān)聽3000端口,server.js的代碼就算完成了。
var http = require('http'); var url = require('url'); var api = require('./route/api'); var static = require('./route/static'); // 匹配靜態(tài)文件夾路徑的正則表達式,用于判定請求是否為靜態(tài)文件請求 var staticExp = /\/public\/(img|css|js)\/[a-z]*\.(jpg|png|gif|css|js)/; http.createServer((req, res) => { var pathname = url.parse(req.url).pathname; if (staticExp.test(pathname)) {// 靜態(tài)文件請求交由static處理 static.get(__dirname + pathname, res); } else if (req.method == 'POST') {// 處理普通post請求 api.post(req, res); } else {// 處理普通get請求 api.get(req, res); } }).listen(3000); console.log('[Server Info] Start server at http://localhost:3000/');
編寫路由
我從簡單的開始,先寫處理靜態(tài)文件請求的路由static。這個路由的邏輯很簡單,只要客戶端想要請求某個靜態(tài)文件(css/js/圖片),就將被請求的文件發(fā)送給客戶端即可。代碼如下所示。有以下幾點需要注意的地方,首先,客戶端請求文件,需要判斷文件是否存在,如果存在才將其發(fā)送給客戶端,不存在則作其他處理(這里我暫時沒做其他處理)。其次,將文件響應(yīng)給客戶端的時候,需要設(shè)置好http報頭的MIME type,這樣文件發(fā)過去之后客戶端才能識別出文件類型從而正確使用。最后,像圖片、音頻等多媒體文件需要用二進制的讀寫方式,所以在響應(yīng)圖片的時候記得加上“binary”。
var fs = require('fs'); var path = require('path'); var MIME = {}; MIME[".css"] = "text/css"; MIME[".js"] = "text/js"; MIME[".jpg"] = "image/jpeg"; MIME[".jpeg"] = "image/jpeg"; MIME[".png"] = "image/png"; MIME[".gif"] = "image/gif"; function get(pathname, res) { if (fs.existsSync(pathname)) { var extname = path.extname(pathname); res.writeHead(200, {'Content-Type': MIME[extname]}); fs.readFile(pathname, (err, data) => { if (err) { console.log(err); res.end(); } else { if (isImage(extname)) { res.end(data, "binary");// 二進制文件需要加上binary } else { res.end(data.toString()); } } }); } } // 根據(jù)拓展名判斷是否為圖片 function isImage(extname) { if (extname === '.jpg' || extname === '.jpeg' || extname === '.png' || extname === '.gif') { return true; } return false; } // 提供給其他模塊使用的接口 module.exports = { get: get };
static寫完了,下面來繼續(xù)寫api。api需要根據(jù)請求的URL來響應(yīng)對應(yīng)的內(nèi)容。例如客戶端請求“/”,就響應(yīng)它網(wǎng)站的主頁,請求“/detail?id=0”就響應(yīng)它id為0的食品的詳情頁面。如果客戶端請求了不存在的URL,則給回一個404響應(yīng),表示沒有找到。代碼如下所示。這里我分了兩個handler,本項目沒有post操作,所以只有g(shù)etHandler會使用到。給出postHanlder的目的是為了簡單說明如何寫處理客戶端post請求的路由。
以getHanlder[‘/']為例,當(dāng)客戶端請求“/”的時候,不是簡單地把index.html響應(yīng)給服務(wù)器這么簡單,想象一下,一家食品店,每天提供的菜式可能會有所不同,或者因為季節(jié)問題而導(dǎo)致每個季節(jié)的特色菜都有所不同,所以我們網(wǎng)站主頁展示的菜式也可能隨之而變化。因此,我們需要根據(jù)數(shù)據(jù)庫中存儲的主頁數(shù)據(jù)來動態(tài)渲染主頁的內(nèi)容。我把idnex.html寫成模板,為了不適用jade等模板引擎,我在html里面使用如同“{{foodMenu}}”這種形式的標(biāo)記,當(dāng)讀取完模板之后,利用簡單的字符串操作將標(biāo)記替換成我們需要動態(tài)渲染的內(nèi)容,即可實現(xiàn)動態(tài)渲染HTML的目的。
靜態(tài)文件之外的其他路由,或者叫控制器(controller),一般都會包含業(yè)務(wù)邏輯,即業(yè)務(wù)邏輯一般是在這一層完成的。像上面的根據(jù)數(shù)據(jù)庫內(nèi)容動態(tài)渲染出首頁,或者你在其他場景下面會見到的如登錄注冊的數(shù)據(jù)檢驗,成功登錄之后將客戶端重定向到對應(yīng)的用戶界面等等業(yè)務(wù)邏輯都是在這一層實現(xiàn)。
var fs = require('fs'); var url = require('url'); var querystring = require('querystring'); var foods = require('../model/foods')(); var detail = require('../model/detail')(); var getHandler = {}; var postHandler = {}; // 處理對主頁的請求 getHandler['/'] = function(req, res) { var foodMenu = ""; // 拼裝首頁數(shù)據(jù) var food = foods.getAllFoods(); for (var i = 0; i < food.length; ++i) { foodMenu += '<div class="food-card" id="' + food[i].id + '"><img src="'; foodMenu += food[i].image + '"><h2>' + food[i].name + '</h2><h3>' + food[i].price + '</h3></div>'; } res.writeHead(200, {"Content-Type": "text/html"}); fs.readFile(__dirname + '/../views/index.html', (err, data) => { if (err) { console.log(err); res.end(); } else { // 動態(tài)渲染模板 res.end(data.toString().replace('{{foodMenu}}', foodMenu)); } }); }; // 處理對詳情頁面的請求 getHandler['/detail'] = function(req, res) { var query = querystring.parse(url.parse(req.url).query); var foodDetail = detail.getDetail(query.id); res.writeHead(200, {"Content-Type": "text/html"}); fs.readFile(__dirname + '/../views/detail.html', (err, data) => { // 動態(tài)渲染模板 res.end(data.toString().replace('{{image}}', foodDetail.image) .replace('{{name}}', foodDetail.name) .replace('{{description}}', foodDetail.description) .replace('{{price}}', foodDetail.price)); }); }; // 404響應(yīng),告知客戶端資源未找到 getHandler['/404'] = function(req, res) { res.writeHead(404, {"Content-Type": "text/plain"}); res.end("404 Not Found"); }; // post請求的處理方法示例 postHandler['/'] = function(res, data) { // do something }; // get請求 function get(req, res) { var reqUrl = url.parse(req.url); if (typeof getHandler[reqUrl.pathname] === "function") { getHandler[reqUrl.pathname](req, res); } else { getHandler["/404"](req, res); } } // post請求(示例) function post(req, res) { var reqUrl = url.parse(req.url); if (typeof postHandler[reqUrl.pathname] === "function") { var postData = ""; req.on('data', (data) => { postData += data; }); req.on('end', () => { postData = querystring.parse(postData); postHandler[reqUrl.pathname](res, postData); }); } else { getHandler["/404"](req, res); } } // 提供給其他模塊使用的接口 module.exports = { get: get, post: post };
最后,講一下post方法的處理過程,雖然本項目中沒有使用到post。post方法跟get方法最主要的不同之處在于post方法除了發(fā)送http頭部信息之外還帶有客戶端提交的數(shù)據(jù)。在接收到post請求的時候,需要將數(shù)據(jù)讀取出來,讀取數(shù)據(jù)的方式也挺簡單,只要給request設(shè)置監(jiān)聽器就行了。當(dāng)request對象收到數(shù)據(jù)的時候會觸發(fā)“data”事件,因此,給這個事件設(shè)置監(jiān)聽器,讓它收到數(shù)據(jù)的時候就把數(shù)據(jù)保存起來。在接收完一個請求全部的post數(shù)據(jù)之后會觸發(fā)“end”事件,因此,給這個事件設(shè)置監(jiān)聽器,使得在接收完全部數(shù)據(jù)之后才開始對提交的數(shù)據(jù)進行相關(guān)的操作。
編寫數(shù)據(jù)模型
先拿主頁來講吧。通過前面的截圖,我們可以知道,主頁上的數(shù)據(jù)包括展示菜品的圖片、名稱、價格,另外需要根據(jù)不同的菜品跳轉(zhuǎn)到對應(yīng)的詳情頁,因此還需要一個id來用作標(biāo)識符。最后,可以得到如下的數(shù)據(jù)模型(下面的模型我使用json描述,你也可以采取其他辦法)。這個數(shù)據(jù)模型描述了主頁的數(shù)據(jù)模型,即首頁有很多個食品foods,用數(shù)組表示,每個數(shù)據(jù)元素代表一個食品。每個食品包括四項信息,id,image,name,price。id的值是一個數(shù)字,作為唯一標(biāo)識符。image是一個字符串,用來指明圖片地址。name的值是字符串,表示食品的名字,price的值是一個字符串,表示食品的價格。
{ "foods": [{ "id": "number", "image": "string", "name": "string", "price": "string" }] }
設(shè)計好數(shù)據(jù)模型的目的是方便我們設(shè)計偽數(shù)據(jù),也方便我們對數(shù)據(jù)進行操作,一般在開始編程之前要做的事情就是設(shè)計好數(shù)據(jù)模型(數(shù)據(jù)結(jié)構(gòu)),這樣寫程序時候才會更加順利,很多接口才能規(guī)范下來。雖然我這里把model這一步放在了最后,但我這里model里面只是寫了數(shù)據(jù)訪問模塊,不代表數(shù)據(jù)模型是最后才設(shè)計的,只是因為我這里講解的思路是自定向下,剛好講到model就順帶提一提數(shù)據(jù)模型設(shè)計。
下面以foods.js為例來講解如何編寫model。代碼如下所示。這里由于沒有數(shù)據(jù)庫(涉及數(shù)據(jù)庫的話對于新手來說比較麻煩,為了講清楚過程本文將不采用數(shù)據(jù)庫存儲數(shù)據(jù)),我將所有數(shù)據(jù)使用json文件存儲,例如foods.json中存儲了主頁的所有食品的數(shù)據(jù)。foods model將對外提供接口,用于支持訪問主頁的食品數(shù)據(jù),修改食品數(shù)據(jù)等操作(數(shù)據(jù)庫常說的增刪查改CRUD四個操作)。本項目只需要用到查詢所有視頻的操作,所以我這里簡單實現(xiàn)了一個獲取所有食品的方法,另外附帶一個根據(jù)id獲取單個食品的方法(這個方法僅是示例,沒有用到)。
var fs = require('fs'); module.exports = function() { // 讀取文件中的數(shù)據(jù),將其轉(zhuǎn)成一個對象方便使用 var data = JSON.parse(fs.readFileSync(__dirname + '/../data/foods.json')); var foods = { getAllFoods: getAllFoods, getFood: getFood }; // 獲取所有食品 function getAllFoods() { return data.foods; } // 根據(jù)id獲取單個食品 function getFood(id) { for (var i = 0; i < data.foods.length; ++i) { if (data.foods[i].id == id) return data.foods[i]; } } return foods; };
model里面的模塊一般提供數(shù)據(jù)操作的服務(wù)供控制器使用,所以在這一層就主要關(guān)注實現(xiàn)數(shù)據(jù)CRUD操作即可,基本沒有什么業(yè)務(wù)邏輯了。
照著寫foods的思路,我們再把detail寫完,整個項目就完成了。是不是挺簡單的。進到項目目錄下面,使用node server.js啟動服務(wù)器跑一跑吧。
最后,看完整個項目,你大概可以發(fā)現(xiàn)整個編寫過程,或者說每個模塊的劃分,都好像遵照某種特定的模式在進行,其實我是按照MVC的模式來編寫這個項目的,最近在另外一門學(xué)課的學(xué)習(xí)中也經(jīng)常用到MVC,覺得還是挺不錯的一種設(shè)計模式,有興趣可以研究一下。當(dāng)然,我不能說我寫的代碼完全符合MVC的規(guī)范,畢竟每個人的理解都可能有那么一些出入。本文僅供參考,歡迎交流建議,謝謝!
感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“NodeJS基礎(chǔ)API搭建服務(wù)器的示例分析”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識等著你來學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。