參考《Node.js開發(fā)指南 ByVoid》 Page83
一、Express簡(jiǎn)介
Node.js 由于不需要另外的 HTTP 服務(wù)器寇漫,因此減少了一層抽象佳簸,給性能帶來不少提升狡逢,但同時(shí)也因此而提高了開發(fā)難度。舉例來說乒疏,我們要實(shí)現(xiàn)一個(gè) POST 數(shù)據(jù)的表單,例如:
<form method="post" action="http://localhost:3000/">
<input type="text" name="title" />
<textarea name="text"></textarea>
<input type="submit" />
</form>
這個(gè)表單包含兩個(gè)字段: title 和 text 饮焦,提交時(shí)以 POST 的方式將請(qǐng)求發(fā)送給http://localhost:3000/
怕吴。假設(shè)我們要實(shí)現(xiàn)的功能是將這兩個(gè)字段的東西原封不動(dòng)地返回給用戶,PHP 只需寫兩行代碼县踢,儲(chǔ)存為 index.php 放在網(wǎng)站根目錄下即可:
echo $_POST['title'];
echo $_POST['text'];
在 3.5.1 節(jié)中使用了類似下面的方法(用 http 模塊):
var http = require('http');
var querystring = require('querystring');
var server = http.createServer(function(req, res) {
var post = '';
req.on('data', function(chunk) {
post += chunk;
});
req.on('end', function() {
post = querystring.parse(post);
res.write(post.title);
res.write(post.text);
res.end();
});
}).listen(3000);
這種差別可能會(huì)讓你大吃一驚转绷,PHP 的實(shí)現(xiàn)要比Node.js容易得多。Node.js 完成這樣一個(gè)簡(jiǎn)單任務(wù)竟然如此復(fù)雜:你需要先創(chuàng)建一個(gè) http 的實(shí)例硼啤,在其請(qǐng)求處理函數(shù)中手動(dòng)編寫req 對(duì)象的事件監(jiān)聽器议经。當(dāng)客戶端數(shù)據(jù)到達(dá)時(shí),將 POST數(shù)據(jù)暫存在閉包的變量中谴返,直到 end事件觸發(fā)煞肾,解析 POST 請(qǐng)求,處理后返回客戶端嗓袱。
其實(shí)這個(gè)比較是不公平的扯旷,PHP 之所以顯得簡(jiǎn)單并不是因?yàn)樗鼪]有做這些事,而是因?yàn)镻HP 已經(jīng)將這些工作完全封裝好了索抓,只提供了一個(gè)高層的接口钧忽,而 Node.js 的 http 模塊提供的是底層的接口,盡管使用起來復(fù)雜逼肯,卻可以讓我們對(duì) HTTP 協(xié)議的理解更加清晰耸黑。但是等等,我們并不是為了理解 HTTP 協(xié)議才來使用 Node.js 的篮幢,作為 Web 應(yīng)用開發(fā)者大刊,我們不需要知道實(shí)現(xiàn)的細(xì)節(jié),更不想與這些細(xì)節(jié)糾纏從而降低開發(fā)效率三椿。難道 Node.js 的抽象如此之差缺菌,把不該有的細(xì)節(jié)都暴露給了開發(fā)者嗎葫辐?
實(shí)際上,Node.js 雖然提供了 http 模塊伴郁,卻不是讓你直接用這個(gè)模塊進(jìn)行 Web 開發(fā)的耿战。http 模塊僅僅是一個(gè) HTTP 服務(wù)器內(nèi)核的封裝,你可以用它做任何 HTTP 服務(wù)器能做的事情焊傅,不僅僅是做一個(gè)網(wǎng)站剂陡,甚至實(shí)現(xiàn)一個(gè) HTTP代理服務(wù)器都行。你如果想用它直接開發(fā)網(wǎng)站狐胎,那么就必須手動(dòng)實(shí)現(xiàn)所有的東西了鸭栖,小到一個(gè) POST 請(qǐng)求,大到 Cookie握巢、會(huì)話的管理晕鹊。當(dāng)你用這種方式建成一個(gè)網(wǎng)站的時(shí)候,你就幾乎已經(jīng)做好了一個(gè)完整的框架了暴浦。
npm 提供了大量的第三方模塊捏题,其中不乏許多 Web 框架,我們沒有必要重復(fù)發(fā)明輪子肉渴,因而選擇使用 Express 作為開發(fā)框架公荧,因?yàn)樗悄壳白罘€(wěn)定、使用最廣泛同规,而且 Node.js 官方推薦的唯一一個(gè) Web 開發(fā)框架循狰。Express 除了為 http 模塊提供了更高層的接口外,還實(shí)現(xiàn)了許多功能券勺,其中包括:
- 路由控制绪钥;
- 模板解析支持;
- 動(dòng)態(tài)視圖关炼;
- 用戶會(huì)話程腹;
- CSRF 保護(hù);
- 靜態(tài)文件服務(wù)儒拂;
- 錯(cuò)誤控制器寸潦;
- 訪問日志;
- 緩存社痛;
- 插件支持见转。
需要指出的是,Express 不是一個(gè)無所不包的全能框架蒜哀,像 Rails 或 Django 那樣實(shí)現(xiàn)了模板引擎甚至 ORM (Object Relation Model斩箫,對(duì)象關(guān)系模型)。它只是一個(gè)輕量級(jí)的 Web 框架,多數(shù)功能只是對(duì) HTTP協(xié)議中常用操作的封裝乘客,更多的功能需要插件或者整合其他模塊來完成狐血。下面用 Express 重新實(shí)現(xiàn)前面的例子:
var express = require('express');
var app = express.createServer();
app.use(express.bodyParser());
app.all('/', function(req, res) {
res.send(req.body.title + req.body.text);
});
app.listen(3000);
可以看到,我們不需要手動(dòng)編寫 req 的事件監(jiān)聽器了易核,只需加載 express.bodyParser()就能直接通過 req.body 獲取 POST 的數(shù)據(jù)了匈织。
二、快速開始
1.安裝Express
首先我們要安裝 Express耸成。如果一個(gè)包是某個(gè)工程依賴,那么我們需要在工程的目錄下使用本地模式安裝這個(gè)包浴鸿,如果要通過命令行調(diào)用這個(gè)包中的命令井氢,則需要用全局模式安裝(關(guān)于本地模式和全局模式,參見 3.3.4節(jié))岳链,因此按理說我們使用本地模式安裝 Express 即可花竞。但是Express 像很多框架一樣都提供了 Quick Start(快速開始)工具,這個(gè)工具的功能通常是建立一個(gè)網(wǎng)站最小的基礎(chǔ)框架掸哑,在此基礎(chǔ)上完成開發(fā)约急。當(dāng)然你可以完全自己動(dòng)手,但我還是推薦使用這個(gè)工具更快速地建立網(wǎng)站苗分。為了使用這個(gè)工具厌蔽,我們需要用全局模式安裝Express,因?yàn)橹挥羞@樣我們才能在命令行中使用它摔癣。運(yùn)行以下命令:$ npm install -g express
但是發(fā)現(xiàn)命令行里用不了express指令奴饮,參考解決windows下npm安裝的模塊執(zhí)行報(bào)錯(cuò):無法將“cnpm”項(xiàng)識(shí)別為 cmdlet、函數(shù)择浊、腳本文件或可運(yùn)行程序的名稱,用
npm list --depth=0 -global
查看已經(jīng)全局安裝的模塊戴卜,也看到express的版本是4.16.3了。然后把C:\Users\ 用戶名\AppData\Roaming\npm目錄加入環(huán)境變量Path重啟琢岩,發(fā)現(xiàn)還是不行啊投剥。又參考windows全局安裝express,無法命令行執(zhí)行担孔。,有提示說江锨,4版本需要安裝express-generatorc才能使用express命令。
這是express 4.X 版本的更新 導(dǎo)致的糕篇。參見 https://github.com/visionmedia/express/wiki/New-features-in-4.x泳桦。使用
$ npm install -g express-generator
后就解決了。
2.建立工程
Express 在初始化一個(gè)項(xiàng)目的時(shí)候需要指定模板引擎娩缰,默認(rèn)支持Jade和ejs灸撰,為了降低學(xué)習(xí)難度我們推薦使用 ejs,同時(shí)暫時(shí)不添加 CSS 引擎和會(huì)話支持。
注:ejs (Embedded JavaScript) 是一個(gè)標(biāo)簽替換引擎浮毯,其語法與 ASP完疫、PHP 相似,易于學(xué)習(xí)债蓝,目前被廣泛應(yīng)用壳鹤。Express默認(rèn)提供的引擎是 jade,它顛覆了傳統(tǒng)的模板引擎饰迹,制定了一套完整的語法用來生成 HTML 的每個(gè)標(biāo)簽結(jié)構(gòu)芳誓,功能強(qiáng)大但不易學(xué)習(xí)“⊙迹可參考知乎 關(guān)于nodejs的模板引擎锹淌,如何選擇 EJS 和 Jade?赠制,簡(jiǎn)單來說EJS看起來更像HTML赂摆,可以直接拿原生的HTML頁面改成模板。
通過命令express --view=ejs microblog
在當(dāng)前目錄中創(chuàng)建子目錄microblog(因版本問題钟些,原書是express -t ejs microblog
烟号,最新的使用方式根據(jù)npm庫上面的說明來操作),然后執(zhí)行npm install,所有package.json中記錄的插件都會(huì)安裝到node_modules文件夾內(nèi)政恍。
//package.json:
{
"name": "microblog",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"cookie-parser": "~1.4.3",
"debug": "~2.6.9",
"ejs": "~2.5.7",
"express": "~4.16.0",
"http-errors": "~1.6.2",
"morgan": "~1.9.0"
}
}
然后在命令行執(zhí)行npm start汪拥,這個(gè)命令會(huì)查找package.json中的scripts。查看bin目錄下的www文件:
var app = require('../app');
var debug = require('debug')('microblog:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
此時(shí)訪問localhost:3000即可訪問示例頁面篙耗。
3.在www中看到引用了app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/users', usersRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
這里把public,routes,views三個(gè)文件夾都引入進(jìn)來了喷楣,下面逐個(gè)分析一下
4.訪問localhost:3000的流程
var indexRouter = require('./routes/index');
app.use('/', indexRouter);
...
//index.js
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
module.exports = router;
將“ / ”路徑映射到 exports.index函數(shù)下。然后調(diào)用模板解析引擎鹤树,翻譯名為 index 的模板铣焊,并傳入一個(gè)對(duì)象作為參數(shù),這個(gè)對(duì)象只有一個(gè)屬性罕伯,即 title: 'Express'曲伊。看一下index.ejs
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>
</body>
</html>
替換內(nèi)容后追他,瀏覽器發(fā)現(xiàn)要獲取 /stylesheets/style.css坟募,因此會(huì)再次向服務(wù)器發(fā)起請(qǐng)求。app.js 中并沒有一個(gè)路由規(guī)則指派到 /stylesheets/style.css邑狸,但 app 通過app.use(express.static(__dirname + '/public')) 配置了靜態(tài)文件服務(wù)器懈糯,因此/stylesheets/style.css 會(huì)定向到 app.js 所在目錄的子目錄中的文件public/stylesheets/style.css
同樣道理,可以看看http://localhost:3000/users
是如何路由的:在users.js中并沒有使用模板单雾,而是直接返回一個(gè)字符串
var express = require('express');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
module.exports = router;