在习惯了使用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个信息,如下图所示。
用户点击任何一项食品就会跳转到对应的详情页,包括食品图片、名称、价格和描述4个信息,如下图所示。
项目结构
项目的文件结构如下所示。
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(项目信息以及启动方法描述)
本文只讲解服务端编程,因此两个简单界面的实现过程这里就不再啰嗦了。假设你已经能够自行完成前端的界面编程,下面开始讲解服务端编程。
编写服务器
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 :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 };