亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

NodeJS基礎API搭建服務器詳細過程記錄

 更新時間:2017年04月01日 12:03:27   作者:去門口罰站  
本文將以一個超小型web項目,來詳細介紹如何使用NodeJS基礎的http, fs, path, url等模塊提供的API來搭建一個簡單的web服務器。具有很好的參考價值。下面跟著小編一起來看下吧

前言

在習慣了使用express框架,jade模板引擎等現成工具來寫代碼之后,很多人對于基本的NodeJS API會慢慢生疏。本文將以一個超小型web項目,來詳細介紹如何使用NodeJS基礎的http, fs, path, url等模塊提供的API來搭建一個簡單的web服務器。當做對NodeJS的一次復習,也為初學NodeJS的開發(fā)者提供一個參考。本文所搭建的項目將不會使用express等后端框架,僅使用最基礎的NodeJS API,按照MVC設計模式的思路進行編碼和講解,交流意見。源代碼地址如下,建議下載源碼邊看博客邊對照源碼才能比較快理解整個過程。https://github.com/hongchh/node-example

項目介紹

有一個簡單的食品店網站,它包括一個主頁index和一個詳情頁detail。主頁展示食品店的所有食品,包括食品圖片、名稱、價格3個信息,如下圖所示。

用戶點擊任何一項食品就會跳轉到對應的詳情頁,包括食品圖片、名稱、價格和描述4個信息,如下圖所示。

項目結構

項目的文件結構如下所示。

node-example
 |--data(存放項目數據的文件夾)
   |--detail.json(存放食品詳情數據)
   |--foods.json(存放首頁食品數據)
 |--model(提供訪問和操作數據服務的數據模型)
   |--detail.js(詳情數據訪問模塊)
   |--foods.js(食品數據訪問模塊)
 |--public(存放css,js,圖片等靜態(tài)文件)
   |--css(存放css文件的文件夾)
   |--img(存放圖片的文件夾)
   |--js(存放js文件的文件夾)
 |--route(路由,控制器)
   |--api(處理普通請求的路由,或者叫控制器)
   |--static(處理靜態(tài)文件請求的路由,或者叫控制器)
 |--views(視圖,即用戶界面)
   |--index.html(主頁界面)
   |--detail.html(詳情頁面)
 |--server.js(服務器啟動文件)
 |--package.json(項目包信息)
 |--README.md(項目信息以及啟動方法描述)

本文只講解服務端編程,因此兩個簡單界面的實現過程這里就不再啰嗦了。假設你已經能夠自行完成前端的界面編程,下面開始講解服務端編程。

編寫服務器

server.js中要完成服務器的創(chuàng)建和啟動,并將請求轉發(fā)給相應的路由去處理。詳細代碼如下所示(假設我們已經有了能夠正常工作的路由,這里采用Top-Down的思路,我們一層一層地往下寫,專注于解決每個層次的問題)。代碼中使用正則表達式來判定客戶端request是否是在請求靜態(tài)文件,如果是,則交給專門處理靜態(tài)文件請求的路由static去處理,否則交給普通請求的路由器api去處理。普通請求根據它的HTTP方法來判斷使用get或者post。最后,設置服務器監(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ā)送給客戶端,不存在則作其他處理(這里我暫時沒做其他處理)。其次,將文件響應給客戶端的時候,需要設置好http報頭的MIME type,這樣文件發(fā)過去之后客戶端才能識別出文件類型從而正確使用。最后,像圖片、音頻等多媒體文件需要用二進制的讀寫方式,所以在響應圖片的時候記得加上“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());
  }
  }
 });
 }
}

// 根據拓展名判斷是否為圖片
function isImage(extname) {
 if (extname === '.jpg' || extname === '.jpeg' ||
 extname === '.png' || extname === '.gif') {
 return true;
 }
 return false;
}
// 提供給其他模塊使用的接口
module.exports = {
 get: get
};

static寫完了,下面來繼續(xù)寫api。api需要根據請求的URL來響應對應的內容。例如客戶端請求“/”,就響應它網站的主頁,請求“/detail?id=0”就響應它id為0的食品的詳情頁面。如果客戶端請求了不存在的URL,則給回一個404響應,表示沒有找到。代碼如下所示。這里我分了兩個handler,本項目沒有post操作,所以只有getHandler會使用到。給出postHanlder的目的是為了簡單說明如何寫處理客戶端post請求的路由。

以getHanlder[‘/']為例,當客戶端請求“/”的時候,不是簡單地把index.html響應給服務器這么簡單,想象一下,一家食品店,每天提供的菜式可能會有所不同,或者因為季節(jié)問題而導致每個季節(jié)的特色菜都有所不同,所以我們網站主頁展示的菜式也可能隨之而變化。因此,我們需要根據數據庫中存儲的主頁數據來動態(tài)渲染主頁的內容。我把idnex.html寫成模板,為了不適用jade等模板引擎,我在html里面使用如同“{{foodMenu}}”這種形式的標記,當讀取完模板之后,利用簡單的字符串操作將標記替換成我們需要動態(tài)渲染的內容,即可實現動態(tài)渲染HTML的目的。

靜態(tài)文件之外的其他路由,或者叫控制器(controller),一般都會包含業(yè)務邏輯,即業(yè)務邏輯一般是在這一層完成的。像上面的根據數據庫內容動態(tài)渲染出首頁,或者你在其他場景下面會見到的如登錄注冊的數據檢驗,成功登錄之后將客戶端重定向到對應的用戶界面等等業(yè)務邏輯都是在這一層實現。

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 = "";
 // 拼裝首頁數據
 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 + '"><h1>' + food[i].name + '</h1><h2>' + food[i].price + '</h2></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響應,告知客戶端資源未找到
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頭部信息之外還帶有客戶端提交的數據。在接收到post請求的時候,需要將數據讀取出來,讀取數據的方式也挺簡單,只要給request設置監(jiān)聽器就行了。當request對象收到數據的時候會觸發(fā)“data”事件,因此,給這個事件設置監(jiān)聽器,讓它收到數據的時候就把數據保存起來。在接收完一個請求全部的post數據之后會觸發(fā)“end”事件,因此,給這個事件設置監(jiān)聽器,使得在接收完全部數據之后才開始對提交的數據進行相關的操作。

編寫數據模型

先拿主頁來講吧。通過前面的截圖,我們可以知道,主頁上的數據包括展示菜品的圖片、名稱、價格,另外需要根據不同的菜品跳轉到對應的詳情頁,因此還需要一個id來用作標識符。最后,可以得到如下的數據模型(下面的模型我使用json描述,你也可以采取其他辦法)。這個數據模型描述了主頁的數據模型,即首頁有很多個食品foods,用數組表示,每個數據元素代表一個食品。每個食品包括四項信息,id,image,name,price。id的值是一個數字,作為唯一標識符。image是一個字符串,用來指明圖片地址。name的值是字符串,表示食品的名字,price的值是一個字符串,表示食品的價格。

{
 "foods": [{
 "id": "number",
 "image": "string",
 "name": "string",
 "price": "string"
 }]
}

設計好數據模型的目的是方便我們設計偽數據,也方便我們對數據進行操作,一般在開始編程之前要做的事情就是設計好數據模型(數據結構),這樣寫程序時候才會更加順利,很多接口才能規(guī)范下來。雖然我這里把model這一步放在了最后,但我這里model里面只是寫了數據訪問模塊,不代表數據模型是最后才設計的,只是因為我這里講解的思路是自定向下,剛好講到model就順帶提一提數據模型設計。

下面以foods.js為例來講解如何編寫model。代碼如下所示。這里由于沒有數據庫(涉及數據庫的話對于新手來說比較麻煩,為了講清楚過程本文將不采用數據庫存儲數據),我將所有數據使用json文件存儲,例如foods.json中存儲了主頁的所有食品的數據。foods model將對外提供接口,用于支持訪問主頁的食品數據,修改食品數據等操作(數據庫常說的增刪查改CRUD四個操作)。本項目只需要用到查詢所有視頻的操作,所以我這里簡單實現了一個獲取所有食品的方法,另外附帶一個根據id獲取單個食品的方法(這個方法僅是示例,沒有用到)。

var fs = require('fs');

module.exports = function() {
 // 讀取文件中的數據,將其轉成一個對象方便使用
 var data = JSON.parse(fs.readFileSync(__dirname + '/../data/foods.json'));
 var foods = {
 getAllFoods: getAllFoods,
 getFood: getFood
 };

 // 獲取所有食品
 function getAllFoods() {
 return data.foods;
 }

 // 根據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里面的模塊一般提供數據操作的服務供控制器使用,所以在這一層就主要關注實現數據CRUD操作即可,基本沒有什么業(yè)務邏輯了。

照著寫foods的思路,我們再把detail寫完,整個項目就完成了。是不是挺簡單的。進到項目目錄下面,使用node server.js啟動服務器跑一跑吧。

最后,看完整個項目,你大概可以發(fā)現整個編寫過程,或者說每個模塊的劃分,都好像遵照某種特定的模式在進行,其實我是按照MVC的模式來編寫這個項目的,最近在另外一門學課的學習中也經常用到MVC,覺得還是挺不錯的一種設計模式,有興趣可以研究一下。當然,我不能說我寫的代碼完全符合MVC的規(guī)范,畢竟每個人的理解都可能有那么一些出入。本文僅供參考,歡迎交流建議,謝謝!

以上就是本文的全部內容,希望本文的內容對大家的學習或者工作能帶來一定的幫助,同時也希望多多支持腳本之家!

相關文章

  • 基于html5和nodejs相結合實現websocket即使通訊

    基于html5和nodejs相結合實現websocket即使通訊

    HTML5 擁有許多引人注目的新特性,如 Canvas、本地存儲、多媒體編程接口、WebSocket 等等。雖然現在大家把它捧的很火的樣子,但是個人認為它還需要其他平臺的支持才能真正的"火起來"
    2015-11-11
  • 在Node.js中處理CORS問題的解決方案

    在Node.js中處理CORS問題的解決方案

    當今的網絡應用程序越來越多地涉及跨域資源共享(CORS)問題,這是因為現代瀏覽器實施了安全策略,限制了跨源HTTP請求,什么是CORS?如何在Node.js中處理CORS問題,需要的朋友可以參考下
    2024-07-07
  • node網頁分段渲染詳解

    node網頁分段渲染詳解

    按照常理,我們渲染一張網頁,必定是網頁全部拼裝完畢,然后生成HTML字符串,傳送至客戶端。這也意味著,如果一張網頁處理的有快有慢的話,必須串行等到所有的邏輯都處理完畢。后端才能進行返回。
    2016-09-09
  • 使用Node.js實現獲取視頻詳情

    使用Node.js實現獲取視頻詳情

    這篇文章主要為大家詳細介紹了如何在Node.js應用程序中實現獲取視頻詳情的功能,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
    2024-04-04
  • 詳解Node 定時器

    詳解Node 定時器

    這篇文章主要介紹了Node 定時器的相關知識,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2018-02-02
  • Node中文件斷點續(xù)傳原理和方法總結

    Node中文件斷點續(xù)傳原理和方法總結

    在之前做過一個小項目,涉及到了文件上傳,在大文件上面使用了斷點續(xù)傳,降低了服務器方面的壓力,現在小編把Node中文件斷點續(xù)傳原理和方法總結分享給大家,感興趣的朋友一起看看吧
    2022-01-01
  • 關于Node.js中的JXcore打包示例

    關于Node.js中的JXcore打包示例

    這篇文章主要介紹了關于Node.js中的JXcore打包示例,JXcore 是一個支持多線程的 Node.js 發(fā)行版本,基本不需要對你現有的代碼做任何改動就可以直接線程安全地以多線程運行,需要的朋友可以參考下
    2023-05-05
  • mac中利用NVM管理不同node版本的方法詳解

    mac中利用NVM管理不同node版本的方法詳解

    這篇文章主要給大家介紹了關于在mac中利用NVM管理不同node版本的相關資料,文中詳細介紹了nvm的安裝和卸載、nvm安裝node的方法,以及nvm如何管理node版本,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧。
    2017-11-11
  • node 可讀流與可寫流的運用詳解

    node 可讀流與可寫流的運用詳解

    這篇文章主要為大家介紹了node 可讀流與可寫流的運用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-09-09
  • nodejs實現套接字服務功能詳解

    nodejs實現套接字服務功能詳解

    這篇文章主要介紹了nodejs實現套接字服務功能,簡單描述了套接字的概念、功能,并結合實例形式分析了nodejs使用socket對象創(chuàng)建及使用套接字進行數據傳輸相關操作技巧與注意事項,需要的朋友可以參考下
    2018-06-06

最新評論