序
在习惯使用express Framework、jade Template Engine等现成工具编写代码后,很多人对基本的NodeJS API变得不熟悉。本文将详细介绍如何利用NodeJS的http、fs、path、url等模块提供的API构建一个简单的web服务器。作为对NodeJS的回顾,也为NodeJS的初学者提供了参考。本文搭建的项目不会使用express等后端框架,只使用最基础的NodeJS API,按照MVC设计模式进行编码和解释,并交换意见。源代码地址如下。建议下载源代码,边看博客边对比源代码,快速了解整个过程。https://github.com/hongchh/node-example
项目介绍
有一个简单的食品店网站,包括一个主页索引和一个详细的页面细节。首页显示了食品店所有的食物,包括食物图片、名称、价格三个信息,如下图所示。
当用户点击任何一种食物时,都会跳转到相应的详细信息页面,包括食物图片、名称、价格和描述四条信息,如下图所示。
项目结构
项目的文件结构如下。
Node-example |-data(存储项目数据的文件夹)|-detail.json(存储食品详细数据)|-foods.json(存储首页食品数据)|-model(提供访问和操作数据服务的数据模型)|-detail.js(详细数据访问模块)| - foods.js(食品数据访问模块)|-public(存储css、js和图片等静态文件)|-css(存储css文件的文件夹)|-img(存储图片的文件夹)|-js(存储js文件的文件夹)Or controller) |-static(处理静态文件请求的路由,或controller)|-view(视图,即用户界面)|-index.html(主页界面)|-detail.html(详细页)|-server.js(服务器启动文件)|-package.json(项目包信息)|-readme.md(项目信息和启动方法说明)本文只讲解服务器编程,所以两个简单接口的实现过程就在这里。假设您已经能够自己完成前端接口编程,让我们开始解释服务器端编程。
创作服务器
Server.js应该完成服务器的创建和启动,并将请求转发到相应的路由进行处理。详细的代码如下所示(假设我们有一个工作路线,这里采用自上而下的思路,一层一层写下来,重点解决每一层的问题)。代码中使用正则表达式来确定客户端请求是否请求静态文件。如果是这样的话,它被交给处理静态文件请求的路由静态,否则,它被交给处理普通请求的路由器api。普通请求根据其HTTP方法使用get或post。最后设置服务器监听端口3000,server.js的代码就完成了。
var http=require(' http ');var URL=require(' URL ');var api=require('。/route/API’);var static=require('。/路由/静态');//匹配静态文件夹路径的正则表达式用于判断请求是否为静态文件请求var static exp=//public /(img | CSS | js)/[a-z]* 。(jpg | png | gif | CSS | js)/;http.createServer((req,RES)={ var pathname=URL . parse(req . URL)。路径名;If(静态exp。test(pathname)){//静态文件请求由static . get(_ dirname pathname,res)处理;} else if (req.method=='POST') {//处理普通的POST请求api.post(req,RES);} else {//api.get(req,res)用于处理普通的get请求;}}).听(3000);console . log('[服务器信息]在http://localhost:3000/'启动服务器);写一条路线
我将从一个简单的角度开始,编写静态路由来处理静态文件请求。这种路由的逻辑非常简单。只要客户端想要请求一个静态文件(css/js/picture),就可以将请求的文件发送给客户端。代码如下所示。有以下几点需要注意。首先,当客户端请求文件时,需要判断文件是否存在。如果存在,会发送给客户端,如果不存在,会做其他处理(我这里暂时没有做其他处理)。其次,在向客户端响应文件时,需要设置http头的MIME类型,以便客户端在文件发送后能够识别文件类型并正确使用。最后,图片、音频等多媒体文件需要二进制读写,所以在响应图片时记得加“二进制”。
var fs=require(' fs ');var path=require(' path ');var MIME={ };MIME[]。CSS ']=' text/CSS ';MIME[]。js ']=' text/js ';MIME[]。JPEG ']=' image/JPEG ';MIME[]。JPEG ']=' image/JPEG ';MIME[]。png']='图像/png ';MIME[]。gif ']=' image/gif ';函数get(pathname,RES){ if(fs . existssync(pathname)){ var ext name=path . ext name(pathname);res.writeHead(200,{ ' Content-Type ' : MIME[ext name]});fs.readFile(路径名,(err,data)={ if(err){ console . log(err);RES . end();} else { if(isi mage(ext name)){ RES . end(data,' binary ');//binary } else { RES . end(data . tostring())应添加到二进制文件中;} } });} }//判断是否是图片函数image (extname) {if (extname==='。jpg' | | extname=='。JPEG' | | extname='。png' | | extname==='。gif '){返回true}返回false}//interface module . exports={ get : get }提供给其他模块;写完静态之后,我们继续写api。api需要根据请求的URL响应相应的内容。比如客户端请求“/”,则响应其网站首页,请求“/明细?Id=0”,它用id 0响应其食物的详细信息页面。如果客户端请求一个不存在的URL,它会给出一个404响应,表示没有找到。代码如下所示。这里我已经划分了两个处理程序。这个项目没有后期操作,所以只有getHandler会使用。postHanlder的目的是简单说明如何编写和处理客户端post请求的路由。
以getHanlder['/']/']为例。当客户端请求“/”时,并不像简单地将index.html响应给服务器那么简单。想象一下,一家食品店提供的菜品可能每天都不一样,或者每个季节的特色菜可能会因为季节问题而不一样,那么我们网站首页显示的菜品可能会有相应的变化。因此,我们需要根据数据库中存储的主页数据动态呈现主页的内容。我以idnex.html为模板。为了不适合jade这样的模板引擎,我在html中使用了“{{foodMenu}}”形式的标签。读取模板后,我用简单的字符串操作,将标签替换成我们需要动态渲染的内容,从而实现动态渲染HTML的目的。
静态文件或控制器之外的路由通常包含业务逻辑,也就是说,业务逻辑通常在这个级别完成。业务逻辑如根据上面的数据库内容动态渲染首页,或者检查登录注册的数据,成功登录后将客户端重定向到对应的用户界面等。您将在其他场景下看到,都是在这一层实现的。
var fs=require(' fs ');var URL=require(' URL ');var query string=require(' query string ');var foods=require('./model/foods’)();var detail=require('./model/detail’)();var GetHandler={ };var PostHandler={ };//处理对主页gethandler ['/']=function (req,RES){ var food menu=' ';//组装首页数据var food=foods . getallfoods();for(var I=0;一.食物.长度;I){ Food Menu=' div class=' food-card ' id=' food[I]。id ' ' img src=' http:foodMenu=食物[i]。图片“h1”食物[i]。名称'/h1h2 '食品[i]。价格'/H2/div ';} res.writeHead(200,{ ' Content-Type ' : ' text/html ' });fs.readFile(__dirname '/./view/index . html ',(err,data)={ if(err){ console . log(err);RES . end();} else {//动态呈现模板res.end (data.tostring()。替换(' {{foodmenu}} ',food menu));} });};//处理详细信息页面的请求get handler['/detail ']=function(req,RES){ varquery=query string . parse(URL . parse(req . URL))。查询);var food detail=detail . getdetail(query . id);res.writeHead(200,{ ' Content-Type ' : ' text/html ' });Fs.readfile (_ _ dirname'/./view/detail . html ',(err,data)={//动态呈现模板res.end (data.tostring()。替换(' {{image}} ',foodDetail.image)。替换(' {{name}} ',foodDetail.name)。替换(' {{description}} ',foodDetail.description)。替换(' {{price}} ',food detail . price));});};//404响应,通知客户端找不到gethandler['/404 ']=function(req,RES) {res. writehead (404,{ ' content-type ' : ' text/plain ' };res.end('404未找到');};//post请求处理方法示例posthandler ['/']=function (RES,data) {//do某物};//get request函数get (req,RES){ var req URL=URL . parse(req . URL);if(getHandler[req URL . pathname]===' function '){ getHandler[req URL . pathname](req,RES);} else { getHandler['/404'](req,RES);}}//post request(示例)函数post (req,RES){ var req URL=URL . parse(req . URL);if(post handler[ReqURl . pathname]===' function '){ var PostDATa=' ';req.on('data ',(data)={ PostDATa=data;});req.on('end ',()={ PostDATa=query string . parse(PostDATa);后置处理器[reqUrl.pathname](res,post data);});} else { getHandler['/404'](req,RES);} }//接口模块。exports={get:get,post 3360 post };最后,我们将谈谈post方法的处理,虽然这个项目中没有使用post。post方法和get方法的主要区别在于,post方法不仅发送http头信息,还携带客户端提交的数据。当您收到post请求时,您需要读取数据,读取数据的方式非常简单,只需为请求设置一个监听器。当请求对象接收数据时,它将触发“数据”事件。因此,为该事件设置了一个侦听器,以便在收到数据时保存数据。接收到请求的所有发布数据后,将触发“结束”事件。因此,为此事件设置了一个侦听器,以便在接收到所有数据后,开始对提交的数据进行相关操作。
编写数据模型
让我们从主页开始。从前面的截图中,我们可以知道主页上的数据包括显示的菜品的图片、名称和价格。另外,我们需要根据不同的菜品跳转到相应的详情页,所以我们还需要一个id作为标识符。最后,我们可以得到下面的数据模型(我用json来描述下面的模型,但是你可以采取其他的措施)。这个数据模型描述了主页的数据模型,即主页上有很多食物,用数组表示,每个数据元素代表一种食物。每种食物都包含四项信息,id、图像、名称和价格。id的值是作为唯一标识符的数字。Image是用于指示图像地址的字符串。name的值是代表食物名称的字符串,price的值是代表食物价格的字符串。
{ ' foods ' :[{ ' id ' : ' number ',' image' :' string ',' name' :' string ',' price' :' string'}]}设计好数据模型的目的是为了方便我们设计虚拟数据和操作数据。虽然我在这里把模型步骤放在了最后,但是我在这里只写了模型中的数据访问模块,并不代表数据模型是在最后设计的,而是因为我在这里解释的思路是自决,所以我在讲模型的时候只是顺便提一下数据模型设计。
下面以foods.js为例说明如何编写模型。代码如下所示。因为这里没有数据库(如果涉及到数据库的话对初学者来说比较麻烦,为了理清流程,本文也不会用数据库来存储数据),所以我会用json文件来存储所有的数据,比如主页上所有食物的数据都存储在foods中,JSON foods模型会提供一个外部接口来支持访问主页上的食物数据、修改食物数据等操作(数据库经常说的是添加、删除、检查、修改CRUD等四个操作)。这个项目只需要查询所有的视频,所以我简单实现了一个获取所有食物的方法,并附上了一个根据id获取单个食物的方法(这个方法只是一个例子,没有使用)。
var fs=require(' fs ');module.exports=function() {//读取文件中的数据并转换为对象,使用方便。vardata=JSON.parse (fs。readfilesync (_ _ dirname'/./data/foods . JSON’);var foods={ getAllFoods 3360 getAllFoods,getFood : getFood };//get all foods function getall foods(){ return data . foods;}//根据id函数get food(id){ for(var I=0;一.数据.食物.长度;i) { if (data.foods[i]。id==id)返回data . foods[I];} }退回食物;};模型中的模块一般提供数据操作服务供控制器使用,所以在这个层面主要关注的是实现数据CRUD操作,基本没有业务逻辑。
按照写美食的思路,我们再把细节完成,整个项目就完成了。是不是很简单。转到项目目录,使用node server.js启动服务器进行运行。
最后,纵观整个项目,大概可以发现整个写作过程,或者说每个模块的划分,似乎都遵循着一定的模式。事实上,我是根据MVC模式编写这个项目的。最近,MVC经常被用于另一门学科的研究。我认为这是一个很好的设计模式,我有兴趣研究它。当然,我不能说我写的代码完全符合MVC规范。毕竟大家可能在理解上有些差异。本文仅供参考,欢迎交流建议,谢谢!
以上就是本文的全部内容。希望本文的内容能给大家的学习或工作带来一些帮助,也希望多多支持我们!