NodeJS基础API搭建服务器详细过程记录


Posted in NodeJs onApril 01, 2017

前言

在习惯了使用express框架,jade模板引擎等现成工具来写代码之后,很多人对于基本的NodeJS API会慢慢生疏。本文将以一个超小型web项目,来详细介绍如何使用NodeJS基础的http, fs, path, url等模块提供的API来搭建一个简单的web服务器。当做对NodeJS的一次复习,也为初学NodeJS的开发者提供一个参考。本文所搭建的项目将不会使用express等后端框架,仅使用最基础的NodeJS API,按照MVC设计模式的思路进行编码和讲解,交流意见。源代码地址如下,建议下载源码边看博客边对照源码才能比较快理解整个过程。https://github.com/hongchh/node-example

项目介绍

有一个简单的食品店网站,它包括一个主页index和一个详情页detail。主页展示食品店的所有食品,包括食品图片、名称、价格3个信息,如下图所示。

NodeJS基础API搭建服务器详细过程记录

用户点击任何一项食品就会跳转到对应的详情页,包括食品图片、名称、价格和描述4个信息,如下图所示。

NodeJS基础API搭建服务器详细过程记录

项目结构

项目的文件结构如下所示。

node-example
 |--data(存放项目数据的文件夹)
   |--detail.json(存放食品详情数据)
   |--foods.json(存放首页食品数据)
 |--model(提供访问和操作数据服务的数据模型)
   |--detail.js(详情数据访问模块)
   |--foods.js(食品数据访问模块)
 |--public(存放css,js,图片等静态文件)
   |--css(存放css文件的文件夹)
   |--img(存放图片的文件夹)
   |--js(存放js文件的文件夹)
 |--route(路由,控制器)
   |--api(处理普通请求的路由,或者叫控制器)
   |--static(处理静态文件请求的路由,或者叫控制器)
 |--views(视图,即用户界面)
   |--index.html(主页界面)
   |--detail.html(详情页面)
 |--server.js(服务器启动文件)
 |--package.json(项目包信息)
 |--README.md(项目信息以及启动方法描述)

本文只讲解服务端编程,因此两个简单界面的实现过程这里就不再??铝恕<偕枘阋丫?芄蛔孕型瓿汕岸说慕缑姹喑蹋?旅婵?冀步夥?穸吮喑獭?/p>

编写服务器

server.js中要完成服务器的创建和启动,并将请求转发给相应的路由去处理。详细代码如下所示(假设我们已经有了能够正常工作的路由,这里采用Top-Down的思路,我们一层一层地往下写,专注于解决每个层次的问题)。代码中使用正则表达式来判定客户端request是否是在请求静态文件,如果是,则交给专门处理静态文件请求的路由static去处理,否则交给普通请求的路由器api去处理。普通请求根据它的HTTP方法来判断使用get或者post。最后,设置服务器监听3000端口,server.js的代码就算完成了。

var http = require('http');
var url = require('url');
var api = require('./route/api');
var static = require('./route/static');

// 匹配静态文件夹路径的正则表达式,用于判定请求是否为静态文件请求
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)) {// 静态文件请求交由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/');

编写路由

我从简单的开始,先写处理静态文件请求的路由static。这个路由的逻辑很简单,只要客户端想要请求某个静态文件(css/js/图片),就将被请求的文件发送给客户端即可。代码如下所示。有以下几点需要注意的地方,首先,客户端请求文件,需要判断文件是否存在,如果存在才将其发送给客户端,不存在则作其他处理(这里我暂时没做其他处理)。其次,将文件响应给客户端的时候,需要设置好http报头的MIME type,这样文件发过去之后客户端才能识别出文件类型从而正确使用。最后,像图片、音频等多媒体文件需要用二进制的读写方式,所以在响应图片的时候记得加上“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写完了,下面来继续写api。api需要根据请求的URL来响应对应的内容。例如客户端请求“/”,就响应它网站的主页,请求“/detail?id=0”就响应它id为0的食品的详情页面。如果客户端请求了不存在的URL,则给回一个404响应,表示没有找到。代码如下所示。这里我分了两个handler,本项目没有post操作,所以只有getHandler会使用到。给出postHanlder的目的是为了简单说明如何写处理客户端post请求的路由。

以getHanlder[‘/']为例,当客户端请求“/”的时候,不是简单地把index.html响应给服务器这么简单,想象一下,一家食品店,每天提供的菜式可能会有所不同,或者因为季节问题而导致每个季节的特色菜都有所不同,所以我们网站主页展示的菜式也可能随之而变化。因此,我们需要根据数据库中存储的主页数据来动态渲染主页的内容。我把idnex.html写成模板,为了不适用jade等模板引擎,我在html里面使用如同“{{foodMenu}}”这种形式的标记,当读取完模板之后,利用简单的字符串操作将标记替换成我们需要动态渲染的内容,即可实现动态渲染HTML的目的。

静态文件之外的其他路由,或者叫控制器(controller),一般都会包含业务逻辑,即业务逻辑一般是在这一层完成的。像上面的根据数据库内容动态渲染出首页,或者你在其他场景下面会见到的如登录注册的数据检验,成功登录之后将客户端重定向到对应的用户界面等等业务逻辑都是在这一层实现。

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 {
  // 动态渲染模板
  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) => {
 // 动态渲染模板
 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方法除了发送http头部信息之外还带有客户端提交的数据。在接收到post请求的时候,需要将数据读取出来,读取数据的方式也挺简单,只要给request设置监听器就行了。当request对象收到数据的时候会触发“data”事件,因此,给这个事件设置监听器,让它收到数据的时候就把数据保存起来。在接收完一个请求全部的post数据之后会触发“end”事件,因此,给这个事件设置监听器,使得在接收完全部数据之后才开始对提交的数据进行相关的操作。

编写数据模型

先拿主页来讲吧。通过前面的截图,我们可以知道,主页上的数据包括展示菜品的图片、名称、价格,另外需要根据不同的菜品跳转到对应的详情页,因此还需要一个id来用作标识符。最后,可以得到如下的数据模型(下面的模型我使用json描述,你也可以采取其他办法)。这个数据模型描述了主页的数据模型,即首页有很多个食品foods,用数组表示,每个数据元素代表一个食品。每个食品包括四项信息,id,image,name,price。id的值是一个数字,作为唯一标识符。image是一个字符串,用来指明图片地址。name的值是字符串,表示食品的名字,price的值是一个字符串,表示食品的价格。

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

设计好数据模型的目的是方便我们设计伪数据,也方便我们对数据进行操作,一般在开始编程之前要做的事情就是设计好数据模型(数据结构),这样写程序时候才会更加顺利,很多接口才能规范下来。虽然我这里把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操作即可,基本没有什么业务逻辑了。

照着写foods的思路,我们再把detail写完,整个项目就完成了。是不是挺简单的。进到项目目录下面,使用node server.js启动服务器跑一跑吧。

NodeJS基础API搭建服务器详细过程记录

最后,看完整个项目,你大概可以发现整个编写过程,或者说每个模块的划分,都好像遵照某种特定的模式在进行,其实我是按照MVC的模式来编写这个项目的,最近在另外一门学课的学习中也经常用到MVC,觉得还是挺不错的一种设计模式,有兴趣可以研究一下。当然,我不能说我写的代码完全符合MVC的规范,毕竟每个人的理解都可能有那么一些出入。本文仅供参考,欢迎交流建议,谢谢!

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持三水点靠木!

NodeJs 相关文章推荐
nodejs文件操作模块FS(File System)常用函数简明总结
Jun 05 NodeJs
Nodejs中自定义事件实例
Jun 20 NodeJs
使用Angular和Nodejs、socket.io搭建聊天室及多人聊天室
Aug 21 NodeJs
NodeJS创建基础应用并应用模板引擎
Apr 12 NodeJs
nodejs基础知识
Feb 03 NodeJs
nodejs读写json文件的简单方法(必看)
Mar 09 NodeJs
nodejs中使用HTTP分块响应和定时器示例代码
Mar 19 NodeJs
nodejs入门教程四:URL相关模块用法分析
Apr 24 NodeJs
nodejs读取并去重excel文件
Apr 22 NodeJs
详解从NodeJS搭建中间层再谈前后端分离
Nov 13 NodeJs
在NodeJs中使用node-schedule增加定时器任务的方法
Jun 08 NodeJs
ubuntu系统下使用pm2设置nodejs开机自启动的方法
May 12 NodeJs
Ajax异步文件上传与NodeJS express服务端处理
Apr 01 #NodeJs
3分钟快速搭建nodejs本地服务器方法运行测试html/js
Apr 01 #NodeJs
nodejs使用express创建一个简单web应用
Mar 31 #NodeJs
nodejs实现邮件发送服务实例分享
Mar 29 #NodeJs
NodeJs测试框架Mocha的安装与使用
Mar 28 #NodeJs
NodeJS测试框架mocha入门教程
Mar 28 #NodeJs
nodejs模块nodemailer基本使用-邮件发送示例(支持附件)
Mar 28 #NodeJs
You might like
打造计数器DIY三步曲(中)
2006/10/09 PHP
PHP 日期加减的类,很不错
2009/10/10 PHP
php图片上传类 附调用方法
2016/05/15 PHP
php利用gd库为图片添加水印
2016/11/09 PHP
解决PHP 7编译安装错误:cannot stat ‘phar.phar’: No such file or directory
2017/02/25 PHP
redis+php实现微博(三)微博列表功能详解
2019/09/23 PHP
js 获取Listbox选择的值的代码
2010/04/15 Javascript
jquery div 居中技巧应用介绍
2012/11/24 Javascript
简单选项卡 js和jquery制作方法分享
2014/02/26 Javascript
JS实现一个列表中包含上移下移删除等功能
2014/09/24 Javascript
DOM 事件流详解
2015/01/20 Javascript
JavaScript实现带标题的图片轮播特效
2015/05/20 Javascript
jquery正则表达式验证(手机号、身份证号、中文名称)
2015/12/31 Javascript
JavaScript实现搜索框的自动完成功能(一)
2016/02/25 Javascript
BootStrap 轮播插件(carousel)支持左右手势滑动的方法(三种)
2016/07/07 Javascript
Extjs gridpanel 中的checkbox(复选框)根据某行的条件不能选中的解决方法
2017/02/17 Javascript
Angular.Js之Scope作用域的学习教程
2017/04/27 Javascript
JavaScript中正则表达式判断匹配规则及常用方法
2017/08/03 Javascript
说说AngularJS中的$parse和$eval的用法
2017/09/14 Javascript
基于vue-element组件实现音乐播放器功能
2018/05/06 Javascript
记一次webpack3升级webpack4的踩坑经历
2018/06/12 Javascript
vue与原生app的对接交互的方法(混合开发)
2018/11/28 Javascript
Vue侦测相关api的实现方法
2019/05/22 Javascript
[45:18]完美世界DOTA2联赛循环赛 PXG vs IO 第二场 11.06
2020/11/09 DOTA
Python格式化压缩后的JS文件的方法
2015/03/05 Python
Python引用传值概念与用法实例小结
2017/10/07 Python
Python图像处理之颜色的定义与使用分析
2019/01/03 Python
Django通用类视图实现忘记密码重置密码功能示例
2019/12/17 Python
Python基于staticmethod装饰器标示静态方法
2020/10/17 Python
你对IPv6了解程度
2016/02/09 面试题
七年级政治教学反思
2014/02/03 职场文书
企业管理毕业生求职信
2014/03/11 职场文书
2014基层党员干部学习全国两会心得体会
2014/03/17 职场文书
Python 快速验证代理IP是否有效的方法实现
2021/07/15 Python
Python字符串常规操作小结
2022/04/03 Python
PostgreSQL 插入INSERT、删除DELETE、更新UPDATE、事务transaction
2022/04/12 PostgreSQL